Storybook: Vuetify 2.0.2 not supported

Created on 29 Jul 2019  路  20Comments  路  Source: storybookjs/storybook

Description:
https://stackoverflow.com/a/56367842
The new style of latest version of vuetify 2.0.2 requires that vuetify be passed into new Vue()

My .storybook/config.js file:

import Vue from 'vue'
import { configure, addDecorator } from '@storybook/vue';
import StoryRouter from 'storybook-vue-router'
import VueRouter from 'vue-router'

// add vuetify
import '../src/plugins/vuetify'

// add router
Vue.use(VueRouter)
addDecorator(StoryRouter())

// add vuetify
addDecorator(() => ({
      template: '<v-app><v-content><story/></v-content></v-app>',
}))

// add stories
function loadStories() {
  const req = require.context('../src/components/stories', true, /.stories.ts$/);
  req.keys().forEach(filename => req(filename));
}

configure(loadStories, module);

My /plugins/vuetify.ts file:

import Vue from 'vue'
import Vuetify from 'vuetify'

Vue.use(Vuetify)

export default new Vuetify({
    theme: {
        dark: true,
        icons: {
            iconfont: 'fa',
        },
        options: {
            customProperties: true,
        },
        themes: {
            dark: {
                blue: '#002579',
                'dark-grey': '#464646',
                green: '#00BE96',
                grey: '#EEE8E8',
                orange: '#FE5F5A',
                primary: '#FE5F5A',
                white: '#FFFFFF',
            },
        },
    },
})```



The problem:
Receiving a seemingly unrelated error:

Cannot read property 'dark' of undefined
TypeError: Cannot read property 'dark' of undefined
at VueComponent.isDark (http://localhost:9001/vendors~main.2f0c8252b2ad5480ce8f.bundle.js:73337:34)
at Watcher.get (http://localhost:9001/vendors~main.2f0c8252b2ad5480ce8f.bundle.js:65400:25)
at Watcher.evaluate (http://localhost:9001/vendors~main.2f0c8252b2ad5480ce8f.bundle.js:65505:21)
at VueComponent.computedGetter [as isDark] (http://localhost:9001/vendors~main.2f0c8252b2ad5480ce8f.bundle.js:65755:17)
at VueComponent.themeClasses (http://localhost:9001/vendors~main.2f0c8252b2ad5480ce8f.bundle.js:105182:29)
at Watcher.get (http://localhost:9001/vendors~main.2f0c8252b2ad5480ce8f.bundle.js:65400:25)
at Watcher.evaluate (http://localhost:9001/vendors~main.2f0c8252b2ad5480ce8f.bundle.js:65505:21)
at Proxy.computedGetter (http://localhost:9001/vendors~main.2f0c8252b2ad5480ce8f.bundle.js:65755:17)
at Proxy.render (http://localhost:9001/vendors~main.2f0c8252b2ad5480ce8f.bundle.js:73354:15)
at VueComponent.Vue._render (http://localhost:9001/vendors~main.2f0c8252b2ad5480ce8f.bundle.js:64491:22)```

vue compatibility with other tools has workaround help wanted question / support

Most helpful comment

I had the same issue. All you have to do is add vuetify to the addDecorator call.

// add vuetify
import vuetifyConfig from '../src/plugins/vuetify'

addDecorator(() => ({
      vuetify: vuetifyConfig,
      template: '<v-app><v-content><story/></v-content></v-app>',
}))

All 20 comments

I had the same issue. All you have to do is add vuetify to the addDecorator call.

// add vuetify
import vuetifyConfig from '../src/plugins/vuetify'

addDecorator(() => ({
      vuetify: vuetifyConfig,
      template: '<v-app><v-content><story/></v-content></v-app>',
}))

legend, thanks jack :)

Decorators are the correct approach.

or if you don't want to import the plugin....

// add vuetify
const vuetifyConfig = new Vuetify({
    theme: {
        dark: false
    }
})

addDecorator(() => ({
      vuetify: vuetifyConfig,
      template: '<v-app><v-content><story/></v-content></v-app>',
}))


@jacklp - how did you add the VuetifyLoaderPlugin? via Webpack or vue.config.js?

@morficus I was encountering the same issue as you. I solved it by creating a separate vuetify config file just for Storybook and using the full installation method for Vuetify inside of Storybook, so it doesn't rely on VuetifyLoaderPlugin.

This way I could get Vuetify working in Storybook while still using VuetifyLoaderPlugin in my main application. Hope that helps.

I was actually able to make it work with A-la-carte setup. See revised config.js at the bottom.

@kyleoliveiro @atgjack
This setup doesn't work for me. Vue won't recognize custom Vuetify components.
[Vue warn]: Unknown custom element: <v-app>...
[Vue warn]: Unknown custom element: <v-card>...
And so on for every Vuetify component. It renders:

<v-app>
  <v-card height="400" width="256" class="mx-auto"></v-card>
</v-app>
  • vuetify: ^2.1.7
  • @storybook/vue: ^5.2.5

I configured Vuetify using Webpack install instructions and followed your advice configuring storybook with it's own plugin config.

My .storybook/webpack.config.js

const path = require('path');

module.exports = async ({ config, mode }) => {

    config.module.rules = [
        ...config.module.rules,
        {
        test: /\.s(c|a)ss$/,
        use: [
            'vue-style-loader',
            'css-loader',
            {
                loader: 'sass-loader',
                options: {
                        implementation: require('sass'),
                        sassOptions: {
                            fiber: require('fibers'),
                            indentedSyntax: true
                        }
                    }
                }
            ]
        }
    ];

    config.resolve.alias = {
        ...config.resolve.alias,
        '@': path.resolve(__dirname, '../src')
    }

    return config;
};

.storybook/config.js below

import Vue from 'vue'
import Vuetify from 'vuetify/lib'
import { configure, addDecorator } from '@storybook/vue'

Vue.use(Vuetify)

addDecorator(() => ({
    vuetify: new Vuetify(),
    template: '<v-app><story/></v-app>'
}))

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

I don't know why v-components wont register globally but I got it to work by following A-la-carte setup to register them explicitly.
I import all from Vuetify, extract components only and register them all.

import Vue from 'vue'
import * as _Vuetify from 'vuetify/lib'
import { configure, addDecorator } from '@storybook/vue'

const Vuetify = _Vuetify.default

const isVueComponent = obj => obj.name === 'VueComponent'

const VComponents = Object.keys(_Vuetify).reduce((acc, key) => {
    if (isVueComponent(_Vuetify[key])) {
        acc[key] = _Vuetify[key]
    }
    return acc
}, {})

Vue.use(Vuetify, {
    components: {
        ...VComponents
    }
})

addDecorator(() => ({
    vuetify: new Vuetify(),
    template: '<v-app><story/></v-app>'
}))

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

I had the same problem and @adambuczek approach reflects mine. I do have an issue however in trying to override scss variable for Vuetify 2.

~Currently, i have a variables.scss that's being imported into my main scss file.~
~Variable.scss contains Vuetify sass variable overrides like `$body-font-family: 'Quicksand-Regular'. However, when inspecting in storybook the applied font is still Roboto. What am i missing?~

The above is incorrect, I was just redeclaring already declared sass variables. I now understand that i need VuetifyLoaderPlugin and the a-la-carte approach so that vuetifyloader can override the sass variables needed for customization.

When i do try the vuetifyloader approach, the it's my custom components that complain about not having a render function.

@morficus I was encountering the same issue as you. I solved it by creating a separate vuetify config file just for Storybook and using the full installation method for Vuetify inside of Storybook, so it doesn't rely on VuetifyLoaderPlugin.

This way I could get Vuetify working in Storybook while still using VuetifyLoaderPlugin in my main application. Hope that helps.

This approach no longer works for Vuetify 2 since there is no more "Full installation method".

I'm having the same issue as @antoniancu; Unable to customize the Vuetify theme for Storybook.

The approach suggested by @atgjack does not work, as Storybook complains that the Vuetify components are not registered.

Please re-open this issue @backbone87

Having the same issue - can't customize anything Vuetify related.

Here's my config:

import Vue from 'vue'
import * as _Vuetify from 'vuetify/lib'
import { configure, addDecorator } from '@storybook/vue'
import { keyBy } from 'lodash';
import Icons from '@/components/icons/index';

const Vuetify = _Vuetify.default

const isVueComponent = obj => obj.name === 'VueComponent'

const VComponents = Object.keys(_Vuetify).reduce((acc, key) => {
    if (isVueComponent(_Vuetify[key])) {
        acc[key] = _Vuetify[key]
    }
    return acc
}, {})

Vue.use(Vuetify, {
    options: {
      customProperties: true,
    },
    icons: {
      iconfont: 'md',
      values: {
        ...keyBy(Icons, 'name'),
      },
    },
    components: {
        ...VComponents
    }
})

addDecorator(() => ({
    vuetify: new Vuetify({
      options: {
        customProperties: true,
      },
      icons: {
        iconfont: 'md',
        values: {
          ...keyBy(Icons, 'name'),
        },
      },
    }),
    template: '<v-app><story/></v-app>'
}))

// automatically import all files ending in *.stories.js
configure(require.context('../src', true, /\.stories\.js$/), module);

ok I got this working with the following config. Hoping this helps someone in the future. The difference between the sass and the scss rules are the semicolon at the end of the imported styles.scss file. One expects semicolons and one doesn't so combining them into 1 loader became impossible.

Frankly getting Storybook working with Vuetify is entirely too much trouble

const path = require('path');

module.exports = async ({ config }) => {

  function resolve(dir) {
    return path.join(__dirname, '..', dir);
  }

  /** removes existing scss rule */
  config.module.rules = config.module.rules.filter(rule =>
    !rule.test.test('.scss')
  )
  config.module.rules.push({
    test: /\.sass$/,
    use: [
      'vue-style-loader',
      'css-loader', {
        loader: 'sass-loader',
        options: {
          implementation: require('sass'),
          data: `
            @import '@/styles/styles.scss'
          `,
        }
      },
    ],
  }, {
    test: /\.scss$/,
    use: [
      'vue-style-loader',
      'css-loader', {
        loader: 'sass-loader',
        options: {
          implementation: require('sass'),
          data: `
            @import '@/styles/styles.scss';
          `,
        }
      },
    ],
  })

  config.resolve = {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      vue$: 'vue/dist/vue.esm.js',
      '@': resolve('src'),
    },
  };

  return config;
}; 

cc @pocka @Aaron-Pool anything we learn from this and improve in the docs or out-of-box config?

Here is how I got vuetify-loader to at least partially auto-import vuetify components.

[email protected]
[email protected]
[email protected]
[email protected]

// vue.config.js
module.exports = {
  transpileDependencies: ["vuetify"],
  pluginOptions: {
    storybook: {
      allowedPlugins: ["VuetifyLoaderPlugin"]
    }
  }
};

Now if I use <v-btn> for example within a component that I import into a story, VBtn is automatically imported and registered by vuetify-loader. No warnings in the console and button looks fine visually.

However, if I use any vuetify component within a story definition itself, I get warnings about components that haven't registered. So if you are to use specific vuetify components within the story file, you have to manually register them:

// config/storybook/preview.js
import Vue from "vue";
import { addDecorator, configure } from "@storybook/vue";
import config from "../../src/plugins/vuetify";
import Vuetify, { VApp } from "vuetify/lib";

Vue.use(Vuetify, {
  components: {
    // Vuetify components used in stories need to be manually registered.
    // vuetify-loader cannot pick them up automatically. Presumably because how
    // storybook requires stories?!
    VApp,
  }
});

addDecorator(() => ({
  // 'config' includes the theme configuration (object) only
  vuetify: new Vuetify(config),
  render() {
    return (
      <v-app>
        <story />
      </v-app>
    );
  }
}));

configure(require.context("../../src", true, /\.stories\.js$/), module);

Maybe someone understands why this behaviour and how to make it easier?

Yes, it is how storybook requires stories and how it interacts with Vue

Yes, it is how storybook requires stories and how it interacts with Vue

can this be explained further? is there a documentation for me to read to understand this more?

addDecorator(() => ({
// 'config' includes the theme configuration (object) only
vuetify: new Vuetify(config),
render() {
return (



);
}
}));

configure(require.context("../../src", true, /.stories.js$/), module);
```

Maybe someone understands why this behaviour and how to make it easier?

Hi @mediafreakch, could you explain what does the parameter vuetify within addDecorator() does? I couldn't find any docs that mentioned that property. Similarly, for configure() function.

I'm abit confused here as I'm not very familiar with storybook mechanics

@morficus I was encountering the same issue as you. I solved it by creating a separate vuetify config file just for Storybook and using the full installation method for Vuetify inside of Storybook, so it doesn't rely on VuetifyLoaderPlugin.

This way I could get Vuetify working in Storybook while still using VuetifyLoaderPlugin in my main application. Hope that helps.

Thanks for that mate!

So I could globally load all vuetify components with:

// .storybook/vuetify_storybook.js
import Vue from 'vue';
import Vuetify from 'vuetify'; // loads all components
import 'vuetify/dist/vuetify.min.css'; // all the css for components
import config from '../src/plugins/vuetifyConfig'; // basic config with theme

Vue.use(Vuetify);

export default new Vuetify(config);

And in case someone is interested the whole configuration:

// .storybook/main.js
const path = require('path');

module.exports = {
  stories: ['../stories/**/*.stories.js'],
  addons: ['@storybook/addon-actions', '@storybook/addon-links', '@storybook/addon-knobs'],

  webpackFinal: async (config, { configType }) => {

    // Use Sass loader for vuetify components
    config.module.rules.push({
      test: /\.s(a|c)ss$/,
      use: ['style-loader', 'css-loader', 'sass-loader'],
      include: path.resolve(__dirname, '../'),
    });

    config.module.rules.push({
      resolve: {
        alias: {
          '@': path.resolve(__dirname, '../src'),
          vue: 'vue/dist/vue.js',
          'vue$': 'vue/dist/vue.esm.js',          
        },
      },
    });

    // Return the altered config
    return config;
  },
};
// .storybook/preview.js
import { addDecorator } from '@storybook/vue';
import vuetify from './vuetify_storybook';

addDecorator(() => ({
  vuetify,
  template: `
    <v-app>
      <v-main>
        <v-container fluid >
          <story/>
        </v-container>
      </v-main>
    </v-app>
    `,
}));

btw. version:

storybook: 5.3.19
vuetify: 2.3.3

Are you guys able to read scoped styles that touches vuetify components?
I'm trying it but with it doesn't work, just without scoped.
Example:

<style scoped lang="scss">
  .v-input--switch__thumb, .v-input--switch__track{
    background: var(--v-primary-base);
  }
</style>

@morficus I was encountering the same issue as you. I solved it by creating a separate vuetify config file just for Storybook and using the full installation method for Vuetify inside of Storybook, so it doesn't rely on VuetifyLoaderPlugin.
This way I could get Vuetify working in Storybook while still using VuetifyLoaderPlugin in my main application. Hope that helps.

Thanks for that mate!

So I could globally load all vuetify components with:

// .storybook/vuetify_storybook.js
import Vue from 'vue';
import Vuetify from 'vuetify'; // loads all components
import 'vuetify/dist/vuetify.min.css'; // all the css for components
import config from '../src/plugins/vuetifyConfig'; // basic config with theme

Vue.use(Vuetify);

export default new Vuetify(config);

And in case someone is interested the whole configuration:

// .storybook/main.js
const path = require('path');

module.exports = {
  stories: ['../stories/**/*.stories.js'],
  addons: ['@storybook/addon-actions', '@storybook/addon-links', '@storybook/addon-knobs'],

  webpackFinal: async (config, { configType }) => {

    // Use Sass loader for vuetify components
    config.module.rules.push({
      test: /\.s(a|c)ss$/,
      use: ['style-loader', 'css-loader', 'sass-loader'],
      include: path.resolve(__dirname, '../'),
    });

    config.module.rules.push({
      resolve: {
        alias: {
          '@': path.resolve(__dirname, '../src'),
          vue: 'vue/dist/vue.js',
          'vue$': 'vue/dist/vue.esm.js',          
        },
      },
    });

    // Return the altered config
    return config;
  },
};
// .storybook/preview.js
import { addDecorator } from '@storybook/vue';
import vuetify from './vuetify_storybook';

addDecorator(() => ({
  vuetify,
  template: `
    <v-app>
      <v-main>
        <v-container fluid >
          <story/>
        </v-container>
      </v-main>
    </v-app>
    `,
}));

btw. version:

storybook: 5.3.19
vuetify: 2.3.3

This solution worked for me.
except that I had to import config in vuetify_storybook.js file from different path as

import config from '../../vue.config';

Thanks a lot @DomDomHaas !

Thank you @DomDomHaas, your set up also worked for me.
I still did run into an issue when creating stories using Vuetify VSelect component. Those of you using @storybook/addon-essentials might encounter the same thing.

Specifically this is what I saw in the Chrome browser console:

TypeError: Cannot read property 't' of undefined
    at VueComponent.listData (vuetify.js:22647)
    at Watcher.get (vue.esm.js:4488)
    at Watcher.evaluate (vue.esm.js:4593)
    at VueComponent.computedGetter [as listData] (vue.esm.js:4845)
    at VueComponent.staticList (vuetify.js:22663)
    at Watcher.get (vue.esm.js:4488)
    at Watcher.evaluate (vue.esm.js:4593)
    at VueComponent.computedGetter [as staticList] (vue.esm.js:4845)
    at VueComponent.genList (vuetify.js:22896)
    at VueComponent.genMenu (vuetify.js:22944)

...

index.js:60 TypeError: Failed to execute 'observe' on 'IntersectionObserver': parameter 1 is not of type 'Element'.
    at Object.inserted (vuetify.js:32620)
    at VueComponent.mounted (vuetify.js:37737)
    at invokeWithErrorHandling (vue.esm.js:1863)
    at callHook (vue.esm.js:4228)
    at Object.insert (vue.esm.js:3148)
    at invokeInsertHook (vue.esm.js:6357)
    at Vue.patch [as __patch__] (vue.esm.js:6576)
    at Vue._update (vue.esm.js:3954)
    at Vue.updateComponent (vue.esm.js:4075)
    at Watcher.get (vue.esm.js:4488)

From process of elimination, I narrowed it down to something to do with Storybook's Docs add-on that comes from including @storybook/addon-essentials in main.js. I wasn't using the Docs add-on anyway so I've disabled it to fix this issue:

module.exports = {
  stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
  addons: [
    "@storybook/addon-links",
    {
      name: "@storybook/addon-essentials",
      options: {
        docs: false
      }
    }
  ]
...
Was this page helpful?
0 / 5 - 0 ratings