storybookjs / addon-styling

A base addon for configuring popular styling tools
MIT License
44 stars 10 forks source link

[Bug] PostCSS + Tailwind = no styles #28

Closed aaronadamsCA closed 1 year ago

aaronadamsCA commented 1 year ago

Describe the bug

I am trying to implement Storybook 7 + PostCSS + Tailwind, but I am not getting any styles at all in our Storybook.

Steps to reproduce the behavior

postcss.config.cjs

/** @type {import("postcss-load-config").Config} */
const config = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};

module.exports = config;

tailwind.config.cjs

/** @type {import("tailwindcss").Config} */
const config = {
  content: ["./src/**/*.{ts,tsx}"],
  theme: {},
  plugins: [],
};

module.exports = config;

If I change the content field to point to the wrong location, I see a warning from this addon when I run storybook dev, so it seems the configs are being read normally.

main.ts

import type { StorybookConfig } from "@storybook/react-webpack5";
import postcss from "postcss";

const main: StorybookConfig = {
  addons: [
    "@storybook/addon-essentials",
    {
      name: "@storybook/addon-styling",
      options: {
        postCss: {
          implementation: postcss,
        },
      },
    },
  ],
  babel: (config) => ({ ...config, rootMode: "upward" }),
  framework: "@storybook/react-webpack5",
  stories: ["../src"],
};

export default main;

A simple postCss: true doesn't work either.

preview.ts

import "tailwindcss/tailwind.css";

import type { Preview } from "@storybook/react";

const preview: Preview = {};

export default preview;

Importing my local "../src/tailwind.css" doesn't work either.

Expected behavior

Styles!

Screenshots and/or logs

image

image

Environment

  "devDependencies": {
    "@storybook/addon-a11y": "7.0.5",
    "@storybook/addon-actions": "7.0.5",
    "@storybook/addon-essentials": "7.0.5",
    "@storybook/addon-styling": "1.0.1",
    "@storybook/react": "7.0.5",
    "@storybook/react-webpack5": "7.0.5",
    "@tailwindcss/forms": "0.5.3",
    "autoprefixer": "10.4.14",
    "postcss": "8.4.21",
    "postcss-load-config": "4.0.1",
    "react": "18.2.0",
    "storybook": "7.0.5",
    "tailwindcss": "3.2.4"
  }
chendong-xie commented 1 year ago

I have the same question @aaronadamsCA

dariusrosendahl commented 1 year ago

We have the same problem here. Upgraded to 7.x and @storybook/addon-styling and now we don't have any styling applied.

Funny thing is some styles are applied when adding import 'tailwindcss/tailwind.css'; to the preview but nothing is happening when importing our own styles (import '../styles/globals.css';).

preview.js

// import 'tailwindcss/tailwind.css';
import '../styles/globals.css';

export const parameters = {
    actions: { argTypesRegex: '^on[A-Z].*' },
    controls: {
        expanded: true,
        hideNoControlsWarning: true,
        matchers: {
            color: /(background|color)$/i,
            date: /Date$/,
        },
    },
};

main.js

module.exports = {
    stories: [
        '../stories/**/*.stories.@(js|jsx|ts|tsx|mdx)',
        '../components/**/*.stories.@(js|jsx|ts|tsx|mdx)',
    ],
    addons: [
        '@storybook/addon-essentials',
        {
            name: '@storybook/addon-styling',
            options: {
                cssModules: true,
                postCss: {
                    implementation: require('postcss'),
                },
            },
        },
        '@storybook/addon-mdx-gfm',
    ],
    framework: {
        name: '@storybook/nextjs',
        options: {},
    },
    docs: {
        autodocs: true,
    },
};

postcss.config.js

module.exports = {
    plugins: {
        'postcss-import': {},
        'tailwindcss/nesting': {},
        tailwindcss: {},
        autoprefixer: {},
        cssnano: { preset: 'default' },
    },
};
ShaunEvening commented 1 year ago

@dariusrosendahl Unless you want to use the theme switcher feature, you don't need to use @storybook/addon-styling. The @storybook/nextjs framework copies the configuration for your next app which works out of the box with postcss and css modules.

Try removing @storybook/addon-styling from your addons array and see if that solves your issue

ShaunEvening commented 1 year ago

Hey @aaronadamsCA 👋

Out of curiosity, do you have a sideEffects array in your package.json?

dariusrosendahl commented 1 year ago

Hey @Integrayshaun ,

I removed @storybook/addon-styling but to no avail. Still no styling, it doesn't seem to pick up the stylesheet imported in preview.js. Is there anything else I'm missing?

aaronadamsCA commented 1 year ago

Hey @aaronadamsCA 👋

Out of curiosity, do you have a sideEffects array in your package.json?

@Integrayshaun, our package currently specifies "sideEffects": false. I believe this is correct, since our exported components should not have any side effects; our UI component package is in a monorepo, it exports only TypeScript components that have not been transpiled, and our Storybook's preview.ts isn't part of our package's API surface.

I will also provide our monorepo root Babel config, as it may now be relevant:

babel.config.json

{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react",
    "@babel/preset-typescript"
  ]
}

We use this primarily to configure the repository's ESLint Babel parser so we can lint modern syntax, however our Storybook uses it as well. I would expect our use of "@babel/preset-env" to disable Webpack tree shaking, per their documentation.

ShaunEvening commented 1 year ago

@aaronadamsCA I've been trying to dig into this a bit more and it looks like when you set sideEffects, Webpack will treeshake css imports out because it decides that the import isn't used.

To resolve this, we have two options. You can include your css files in sideEffects like this:

{
  sideEffects: [
    '**/*.css',
  ],
}

or, I can try out setting the css webpack rule to consider all css files as side effects. However, I'll need some help from folks like you having the issue to help me test before I publish a change for that

aaronadamsCA commented 1 year ago

I think it might be a better idea to first figure out why import "tailwindcss/tailwind.css" doesn't work. I just tried it again to be sure, and the result is still no Storybook styles at all, regardless of the value (or absence) of sideEffects.

ShaunEvening commented 1 year ago

@aaronadamsCA and @dariusrosendahl are you able to share a small reproduction with me to help nail this down?

aaronadamsCA commented 1 year ago

@Integrayshaun Here you go!

https://github.com/aaronadamsCA/storybookjs-addon-styling-issue-28

This works fine when importing the local tailwind.css, but not when importing "tailwindcss/tailwind.css".

oliviervanbulck commented 1 year ago

Same problem with NextJS. Just installed a fresh install of Storybook 7 to create a component library, but it seems none of the tailwind classes are compiled and used.

Might be handy to know: When I change the extension of the style file to pcss instead of css, I get following error:

Module parse failed: Unexpected character '@' (1:0)

Which is the line where tailwind is importing @tailwind base;

felixmokross commented 1 year ago

To resolve this, we have two options. You can include your css files in sideEffects like this:

{
  sideEffects: [
    '**/*.css',
  ],
}

Thanks @Integrayshaun, this works for me as a workaround.

image
dariusrosendahl commented 1 year ago

Thank you so much @Integrayshaun ,sideEffects was the answer here too!:

"sideEffects": [
  "./styles/globals.css"
]

Storybook 7.0.x + Tailwind 3.3.x working when this is enabled.

ShaunEvening commented 1 year ago

That's great news @dariusrosendahl and @felixmokross!!! I'll update the webpack rules to consider CSS files side effects so that you don't need to declare them like this.

@aaronadamsCA Thank you for putting that together :) I'll have a look at this today

ShaunEvening commented 1 year ago

Hey @aaronadamsCA 👋

It looks like it has something to do with how pnpm is optimizing your dependencies 🤔 when I switched over to using yarn it worked no problem.

I'll have to look into what we can do to make this work properly for pnpm as well

aaronadamsCA commented 1 year ago

It looks like it has something to do with how pnpm is optimizing your dependencies 🤔 when I switched over to using yarn it worked no problem.

@Integrayshaun Very interesting, thank you. This led me to wonder if the problem might be in @storybook/react-webpack5, but when I remove this plugin, external CSS imports work fine:

https://github.com/aaronadamsCA/storybookjs-addon-styling-issue-28/compare/main...no-styling-plugin

In the meantime at least there's a good workaround by just using a local CSS file. I'd try to help further but I'm in over my head as is.

julpat commented 1 year ago

It looks like it has something to do with how pnpm is optimizing your dependencies

hi @Integrayshaun I have the same issue but with Lerna. After migrating from Storybook v6 to v7 and implementing all the stuff needed, only the styling is broken. A "tailwind" css file loaded from a different package in our mono repo is being ignored (sideEffects did not help). When copypasted the file into the package where Storybook is, and loading it locally works fine (Except for "rebuilding" message in a terminal which goes crazy and never settles down - probably a infinite hot-reload? But not sure why. )

// preview.ts
// does not work - no styles at all
import '@our-monorepo/tailwind/style.css';

// works - I can see the styles
import '../local-copy-of-style.css';

image

// main.ts (other config files are similar to Aaron's mentioned in the initial post
import type { StorybookConfig } from '@storybook/react-webpack5';

const config: StorybookConfig = {
  staticDirs: ['../src'],
  stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    {
      name: '@storybook/addon-styling',
      options: {
        postCss: true,
      },
    },
    '@storybook/addon-mdx-gfm',
  ],
  framework: {
    name: '@storybook/react-webpack5',
    options: {},
  },
  docs: {
    autodocs: true,
  },
};

export default config;
roelandxyz commented 1 year ago

When copypasted the file into the package where Storybook is, and loading it locally works fine

Thanks, this worked for me in Turborepo and @storybook/nextjs

oliviervanbulck commented 1 year ago

I still have this problem, no options mentioned here seems to work. I also tried the official set-up guides with all possible combinations and configurations. Does anyone have an idea?

Main.ts:

import type { StorybookConfig } from '@storybook/nextjs';
import path from 'path';

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

const config: StorybookConfig = {
  stories: [
    '../stories/**/*.mdx',
    '../(stories|components)/**/*.stories.@(js|jsx|ts|tsx)'
  ],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/addon-interactions',
    // '@storybook/addon-styling',
    {
      name: '@storybook/addon-styling',
      options: {
        postCss: {
          implementation: require('postcss')
        }
      }
    }
  ],
  framework: {
    name: '@storybook/nextjs',
    options: {}
  },
  docs: {
    autodocs: 'tag'
  },
  staticDirs: ['../public'],
  webpackFinal: (config) => {
    /**
     * Add support for alias-imports
     * @see https://github.com/storybookjs/storybook/issues/11989#issuecomment-715524391
     */
    config.resolve!.alias = {
      ...config.resolve?.alias,
      '@': [path.resolve(__dirname, '../src/'), path.resolve(__dirname, '../')]
    };

    /**
     * Fixes font import with /
     * @see https://github.com/storybookjs/storybook/issues/12844#issuecomment-867544160
     */
    config.resolve!.roots = [
      path.resolve(__dirname, '../public'),
      'node_modules'
    ];

    config.module!.rules!.push({
      test: /\.scss$/,
      use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader']
    });

    config.module!.rules!.push({
      test: /\.css$/,
      use: [
        {
          loader: 'postcss-loader',
          options: {
            postcssOptions: {
              plugins: [require('tailwindcss'), require('autoprefixer')]
            }
          }
        }
      ],
      include: path.resolve(__dirname, '../')
    });

    config.resolve!.plugins = [
      ...(config.resolve!.plugins || []),
      new TsconfigPathsPlugin({
        extensions: config.resolve!.extensions
      })
    ];

    return config;
  }
};
export default config;

preview.tsx:

import React from 'react';
import type { Preview } from '@storybook/react';
import '../styles/main.css';
import { ThemeProvider } from '@mui/material';
import { lightTheme } from '../utils/theme';

import * as nextImage from 'next/image';

Object.defineProperty(nextImage, 'default', {
  configurable: true,
  value: (props) => <img {...props} />
});

const preview: Preview = {
  decorators: [
    (story: any) => <ThemeProvider theme={lightTheme}>{story()}</ThemeProvider>
  ],
  parameters: {
    actions: { argTypesRegex: '^on.*' },
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/
      }
    }
  }
};

export default preview;

tailwind.config.js:

const colors = require('tailwindcss/colors');

/** @type {import('tailwindcss').Config} */
module.exports = {
  mode: 'all',
  content: [
    './pages/**/*.{js,ts,jsx,tsx}',
    './components/**/*.{js,ts,jsx,tsx}',
    './views/**/*.{js,ts,jsx,tsx}',
    './hooks/**/*.{js,ts,jsx,tsx}'
  ],
  important: '#__next',
  theme: {
    extend: {
      colors: {
        primary: {
          lightest: '#82D4E9',
          light: '#51C5E1',
          DEFAULT: '#00b6da',
          dark: '#009bbe'
        },
        secondary: {
          lightest: colors.gray[100],
          light: colors.gray[200],
          DEFAULT: colors.gray[800],
          dark: colors.gray[900]
        },
        success: {
          lightest: colors.green[100],
          light: colors.green[200],
          DEFAULT: colors.green[800],
          dark: colors.green[900]
        },
        warning: {
          lightest: colors.yellow[100],
          light: colors.yellow[200],
          DEFAULT: colors.yellow[800],
          dark: colors.yellow[900]
        },
        waiting: {
          lightest: colors.purple[100],
          light: colors.purple[200],
          DEFAULT: colors.purple[800],
          dark: colors.purple[900]
        },
        info: {
          lightest: colors.blue[100],
          light: colors.blue[200],
          DEFAULT: colors.blue[800],
          dark: colors.blue[900]
        },
        error: {
          lightest: colors.red[100],
          light: colors.red[200],
          DEFAULT: colors.red[800],
          dark: colors.red[900]
        },
        'routty-blue': {
          lightest: '#82D4E9',
          light: '#51C5E1',
          DEFAULT: '#00b6da'
        },
        'routty-grey': {
          light: '#f5f5f5',
          dark: '#323537'
        }
      },
      backgroundImage: {
        routty: "url('/images/onboarding/onboarding-background.svg')"
      },
      backgroundSize: { 'size-70': '70%' },
      ringColor: '#00b6da',
      keyframes: {
        slideUpAndFade: {
          from: { opacity: 0, transform: 'translateY(2px)' },
          to: { opacity: 1, transform: 'translateY(0)' }
        },
        slideRightAndFade: {
          from: { opacity: 0, transform: 'translateX(-2px)' },
          to: { opacity: 1, transform: 'translateX(0)' }
        },
        slideDownAndFade: {
          from: { opacity: 0, transform: 'translateY(-2px)' },
          to: { opacity: 1, transform: 'translateY(0)' }
        },
        slideLeftAndFade: {
          from: { opacity: 0, transform: 'translateX(2px)' },
          to: { opacity: 1, transform: 'translateX(0)' }
        }
      },
      animation: {
        slideUpAndFade: 'slideUpAndFade 400ms cubic-bezier(0.16, 1, 0.3, 1)',
        slideRightAndFade:
          'slideRightAndFade 400ms cubic-bezier(0.16, 1, 0.3, 1)',
        slideDownAndFade:
          'slideDownAndFade 400ms cubic-bezier(0.16, 1, 0.3, 1)',
        slideLeftAndFade: 'slideLeftAndFade 400ms cubic-bezier(0.16, 1, 0.3, 1)'
      }
    }
  },
  plugins: [],
  safelist: [
    {
      pattern:
        /(bg|border|text)-(primary|secondary|success|warning|waiting|info|error)*/
    }
  ]
};

Story:

import { Badge } from '@components/badge';
import type { Meta, StoryObj } from '@storybook/react';

const meta: Meta<typeof Badge> = {
  title: 'Components/Badge',
  component: Badge,
  tags: ['autodocs'],
  parameters: {
    actions: false
  },
  argTypes: {
    color: {
      control: 'select',
      options: [
        'primary',
        'secondary',
        'error',
        'warning',
        'info',
        'success',
        'waiting'
      ]
    }
  }
};

export default meta;
type Story = StoryObj<typeof Badge>;

export const Primary: Story = {
  args: {
    text: 'Status'
  }
};

package.json:

{
  "name": "rex-fe",
  "version": "0.1.0",
  "private": true,
  "sideEffects": ["**/*.css"],
  ...
}
ShaunEvening commented 1 year ago

Hey @oliviervanbulck 👋

@storybook/nextjs configures postcss and scss for you so when you try to configure them with addon-styling you're doubling up and breaking it. You're also adding more rules to process css and scss files a third time in your webpackFinal function.

If you pull those out, you should be up and running.

import type { StorybookConfig } from '@storybook/nextjs';
import path from 'path';

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

const config: StorybookConfig = {
  stories: [
    '../stories/**/*.mdx',
    '../(stories|components)/**/*.stories.@(js|jsx|ts|tsx)'
  ],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/addon-interactions',
    '@storybook/addon-styling', // Leave this in for theme switching feature
  ],
  framework: {
    name: '@storybook/nextjs',
    options: {}
  },
  docs: {
    autodocs: 'tag'
  },
  staticDirs: ['../public'],
  webpackFinal: (config) => {
    /**
     * Add support for alias-imports
     * @see https://github.com/storybookjs/storybook/issues/11989#issuecomment-715524391
     */
    config.resolve!.alias = {
      ...config.resolve?.alias,
      '@': [path.resolve(__dirname, '../src/'), path.resolve(__dirname, '../')]
    };

    /**
     * Fixes font import with /
     * @see https://github.com/storybookjs/storybook/issues/12844#issuecomment-867544160
     */
    config.resolve!.roots = [
      path.resolve(__dirname, '../public'),
      'node_modules'
    ];

    config.resolve!.plugins = [
      ...(config.resolve!.plugins || []),
      new TsconfigPathsPlugin({
        extensions: config.resolve!.extensions
      })
    ];

    return config;
  }
};
export default config;
ShaunEvening commented 1 year ago

Closing for inactivity. If this is still an issue, we can reopen the issue to triage again