giraud florent

storybook + tailwind + nuxt one webpack config

Create only one webpcack config when using storybook + nuxt

At Vue Montreal we are using nuxt a lot.

Today i want to share with you something i feel it will help you as us. We wanted to add storybook to our project.

Everytime i had to create a webpack just for using storybook. And i faced a lot of bugs. And more when i wanted to use tailwind + postcss ^^.

So let's say you have a basic nuxt config with tailwind

So you should have:

  • '@nuxtjs/tailwindcss' in your buildModules
  • in your assets/css/tailwind.css
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';

And that's all.

Now you want to implement storybook. You want to add tailwind so postcss and not creating a new webpack or at least create another full configuration...

You have to create

.storybook/main.js
.storybook/preview.js

.storybook/preview.js

import Vue from 'vue'
import { configure } from '@storybook/vue'
import '@/assets/css/tailwind.css'

Vue.component('nuxt-link', {
  props:   ['to'],
  methods: {
    log() {
      action('link target')(this.to)
    },
  },
  template: '<a href="#" @click.prevent="log()"><slot>NuxtLink</slot></a>',
})

configure(require.context('../components', true, /\.stories\.js$/), module);

It's basic. I Added nuxt-link for not having some bug because storybook don't know about nuxt-link. As you see i added import '@/assets/css/tailwind.css'.

and the most impostant.

.storybook/main.js

const { getWebpackConfig } = require('nuxt')

module.exports = {
    // `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.
    webpackFinal: async (sbWebpack, { configType }) => {

        const nuxtWebpack = await getWebpackConfig('client', {
            for: process.env.NODE_ENV === 'production' ? 'build' : 'dev'
        })

        const recomposedWebpackConfig = {
            mode: nuxtWebpack.mode,
            devtool: nuxtWebpack.devtool,
            entry: sbWebpack.entry,
            output: sbWebpack.output,
            bail: sbWebpack.bail,
            module: {
                rules: [
                    ...nuxtWebpack.module.rules.map(el => {
                        const reg = RegExp(el.test);
                        if (reg.test(".postcss") || reg.test(".css")) {
                            el.oneOf = el.oneOf.map(e => {
                                e.use.push({
                                    loader: 'postcss-loader',
                                    options: {
                                        ident: 'postcss',
                                        plugins: [
                                        require('tailwindcss')('./tailwind.config.js'),
                                        require('autoprefixer'),
                                        ],
                                    },
                                })
                                return e;
                            })
                        }
                        return el;
                    })
                ]
            },
            plugins: sbWebpack.plugins,
            resolve: {
                extensions: nuxtWebpack.resolve.extensions,
                modules: nuxtWebpack.resolve.modules,
                alias: {
                    ...nuxtWebpack.resolve.alias,
                    ...sbWebpack.resolve.alias,
                },
            },
            optimization: sbWebpack.optimization,
            performance: {
                ...sbWebpack.performance,
                ...nuxtWebpack.performance
            }
        }

        return recomposedWebpackConfig;
    },
};

The 'problem' here is you need some of storybook webpack and nuxt webpack. So i decided to recompose a webpack config with storybook custom webpack.

And THIS. A new feature from nuxt that expose nuxt webpack config with getWebpackConfig. It return a promise and it's a valid webpack export as you can export promise now too INFO HERE.

The most important part for you here is:

module: {
 rules: [
  ...nuxtWebpack.module.rules.map(el => {
    const reg = RegExp(el.test);
    if (reg.test(".postcss") || reg.test(".css")) {
      el.oneOf = el.oneOf.map(e => {
       e.use.push({
        loader: 'postcss-loader',
        options: {
         ident: 'postcss',
         plugins: [
          require('tailwindcss')('./tailwind.config.js'),
          require('autoprefixer'),
         ],
        },
      })
      return e;
     })
    }
    return el;
   })
  ]
},

What you need to update are .postcss and .css rules.

And that's it! Now you should be able to write tailwind / postcss in your component with "ONE CONFIG" :).

This is my workaround. Maybe it's not the best so tell me in comments.

ps: An ISSUE in @nuxtjs/tailwind about adding support with storybook.