I struggled to get Storybook to work with Vuetify and Vue CLI 3. In this repository I have documented how I got it up and running the correct way. Hopefully it will give others a kick start.
- Inspiration
- Built with
- Important
- Useful links
- Create project manually
- Addons
- Project setup
- Misc
- Roadmap
- Contributing
- Support & brag about us
- Author
The solution was inspired by vue-vuetify-storybook, which shows a working example of Vue CLI 3, Vuetify and Storybook and a lot of other goodies. I tried to setup Storybook with my own repository taking this repo as an example, but I failed. The problem with this repository is, that it does not explain how it works. It also uses different configurations for the application itself and Storybook. This means that a component can work for the one but not for the other. I wanted a shared configuration, so that when a component works in either you no it works for the other. That shared configuration is the ./src/plugins/vuetify.js
plugin file that initializes Vuetify.
So you have the choice to follow along and configure your project yourself and really understand how it works or just clone and use the repository. The choice is yours. Are you going to choose the red pill or the blue pill?
- Vue CLI 3 v3.0.4
- Vue 2 v2.5.21
- Vuetify v1.3.0 using the a la carte setup
- Storybook v4.1.0
- Material Design Icons (MDI)
- vue-types
The following two points are key to getting Storybook to work correctly with Vuetify:
-
The Vuetify components must be imported individually from 'vuetify/lib' and added as components to
Vue.use
when initializing Vuetify, see the Vuetify documentation for more information. This is done in the./src/plugins/vuetify.js
plugin file. If a story isn't displaying correctly in Storybook and it does work correctly when running normally, always check that all Vuetify components it uses are imported!IMPORTANT Storybook does not work with
vuetify-loader
, at least I did not get it to work. -
The font must be imported using the
preview-head.html
file.
The following section describes the steps that need to be execute to manually create a project with Vue CLI 3, Vuetify and vue-cli-plugin-vuetify.
-
Create a Vue project:
$ mkdir sb-test & cd sb-test $ vue create .
With the following options:
Vue CLI v3.0.4 ? Please pick a preset: Manually select features ? Check the features needed for your project: Babel, Router, Vuex, CSS Pre-processors, Linter, Unit ? Use history mode for router? (Requires proper server setup for index fallback in production) Yes ? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Stylus ? Pick a linter / formatter config: Prettier ? Pick additional lint features: Lint on save ? Pick a unit testing solution: Jest ? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedicated config files ? Save this as a preset for future projects? (y/N) n
-
Add Vuetify:
$ vue add vuetify
With the following options:
? Choose a preset: Configure (advanced) ? Use a pre-made template? (will replace App.vue and HelloWorld.vue) Yes ? Use custom theme? Yes ? Use custom properties (CSS variables)? Yes ? Select icon font Material Design Icons ? Use fonts as a dependency (for Electron or offline)? No ? Use a-la-carte components? Yes ? Select locale English
-
Install the loaders required by a la carte:
$ npm install --save-dev stylus stylus-loader style-loader css-loader
-
Add the Material Design Icons (MDI):
$ npm install --save-dev @mdi/font
Add the following to the
./src/main.js
file:// Ensure you have installed the `css-loader` import '@mdi/font/css/materialdesignicons.css'
Remove the following from the
./public/index.html
file:<link rel="stylesheet" href="https://cdn.materialdesignicons.com/2.5.94/css/materialdesignicons.min.css">
-
Add the following Vuetify component to
./src/components/common/StatsCard.vue
:<template> <v-card> <v-card-text class="pa-0"> <v-container class="pa-0"> <v-layout row ma-0> <v-flex xs6 align-center justify-center py-3> <v-layout column ma-0 justify-center align-center> <v-icon size="56px" :color="color">{{ icon }}</v-icon> </v-layout> </v-flex> <v-flex xs6 class="text-xs-center py-3 white--text" :class="color"> <div class="headline">{{ title }}</div> <span class="caption">{{ subTitle }}</span> </v-flex> </v-layout> </v-container> </v-card-text> </v-card> </template> <script> export default { name: 'stats-card', props: { icon: { type: String, required: false, default: 'mdi-information-outline' }, title: { type: String, required: true }, subTitle: { type: String, required: true }, color: { type: String, required: false, default: 'grey' } } } </script>
-
Add the following to
./src/components/HelloWorld.vue
:To the
template
:<v-flex xs12 my-3> <v-layout justify-center row > <v-flex xs4 pa-2> <stats-card title="1.200" sub-title="assigned credits" color="primary" icon="mdi-plus" /> </v-flex> <v-flex xs4 pa-2> <stats-card title="1.000" sub-title="used credits" color="secondary" icon="mdi-minus" /> </v-flex> <v-flex xs4 pa-2> <stats-card title="200" sub-title="remaining credits" color="accent" icon="mdi-sigma" /> </v-flex> </v-layout> </v-flex>
To the
script
:import StatsCard from '@/components/common/StatsCard' export default { components: { StatsCard } }
-
Add
--open
to the following entry in thepackage.json
file, so the browser is automatically opened on start:{ "scripts": { "serve": "vue-cli-service serve --open" } }
-
Run lint to correct formatting:
$ npm run lint
-
Test if the initial project is working correctly:
$ npm run serve
-
Add Storybook using Vue CLI 3 plugin vue-cli-plugin-vuetify. The plugin is installed automatically by Vue CLI 3 when executing the following command:
$ vue add storybook
The Storybook plugin injects two additional commands,
storybook:build
andstorybook:serve
into vue-cli-service. You can see all injected Vue CLI 3 commands by running:$ npx vue-cli-service help
You can also learn about the available options of each command with:
$ npx vue-cli-service help [command]
-
Replace the contents of
./config/storybook/config.js
file with the following:/* eslint-disable import/no-extraneous-dependencies */ import { configure, addDecorator } from "@storybook/vue"; import "@/plugins/vuetify"; import "@mdi/font/css/materialdesignicons.css"; // Ensures every story is wrapped in a v-app tag addDecorator(() => ({ template: "<v-app><story/></v-app>" })); const req = require.context("@/stories", true, /.stories.js$/); function loadStories() { req.keys().forEach(filename => req(filename)); } configure(loadStories, module);
There are three things to note here:
- Vuetify is initialized using the
./src/plugins/vuetify.js
file. - The icons are loaded with the
import "@mdi/font/css/materialdesignicons.css";
statement. - The decorator wraps every story in the
v-app
tag ensuring Vuetify tags are displayed correctly.
- Vuetify is initialized using the
-
Add the following to
./src/stories/common.stories.js
:/* eslint-disable import/no-extraneous-dependencies */ import { storiesOf } from '@storybook/vue' import StatsCard from '@/components/common/StatsCard.vue' storiesOf('StatsCard', module) .add('stats-card default color', () => ({ components: { StatsCard }, template: '<v-container grid-list-xl fluid><v-layout row wrap><v-flex xs4><stats-card title="1.200" sub-title="assigned credits"/></v-flex></v-layout></v-container>' })) .add('stats-card green color', () => ({ components: { StatsCard }, template: '<v-container grid-list-xl fluid><v-layout row wrap><v-flex xs4><stats-card title="1.200" sub-title="assigned credits" color="green" icon="mdi-plus" /></v-flex></v-layout></v-container>' }))
-
Run lint to correct formatting:
$ npm run lint
-
Test:
$ npm run storybook:serve
This section only describes those addon's that require special instructions to work correctly with Vue or Vuetify.
The Backgrounds Addon is used to change background colors inside the preview in Storybook. To get the background addon to work with Vuetify requires a hack to VApp, as it sets and controls the background color. To let the background addon control the background color we need to set the background of VApp to transparent. In ./config/storybook/config.js
change the following:
addDecorator(() => ({
template: '<v-app><story/></v-app>',
style: '.theme--light.application { background-color: transparent; }'
}))
To:
addDecorator(() => ({
template: '<v-app style="background-color: transparent;"><story/></v-app>',
style: '.theme--light.application { background-color: transparent; }'
}))
The Viewport Addon allows stories to be displayed in different sizes and layouts in Storybook. This helps build responsive components inside of Storybook. Vuetify has a 12 point grid system. Built using flex-box, the grid is used to layout an application's content. It contains 5 types of media breakpoints that are used for targeting specific screen sizes and orientations. These media types can be added to the viewports of the Viewport addon to simplify testing how Vuetify components respond to different media breakpoints.
Add the following to the ./config/storybook/config.js
file:
import { configureViewport, INITIAL_VIEWPORTS } from '@storybook/addon-viewport'
const vuetifyViewports = {
VuetifyLg: {
name: 'Vuetify LG',
styles: {
width: '1904px'
},
type: 'desktop'
},
VuetifyXs: {
name: 'Vuetify XS',
styles: {
width: '600px'
},
type: 'mobile'
},
VuetifySm: {
name: 'Vuetify SM',
styles: {
width: '960px'
},
type: 'mobile'
},
VuetifyMd: {
name: 'Vuetify MD',
styles: {
width: '1264px'
},
type: 'tablet'
},
VuetifyXl: {
name: 'Vuetify XL',
styles: {
width: '4096px'
},
type: 'desktop'
}
}
configureViewport({
defaultViewport: 'VuetifyMd',
viewports: {
...vuetifyViewports,
...INITIAL_VIEWPORTS
}
})
Note Vuetify MD viewport is set as default, so that it is selected when a story is opened.
This Storysource Addon is used to show stories source in the Storyboard addon panel. Getting it to work with Vue CLI 3 is tricky as it abstracts away the webpack config. The internal webpack config is maintained using webpack-chain . The library provides an abstraction over the raw webpack config, with the ability to define named loader rules and named plugins, and later "tap" into those rules and modify their options. To tweak the webpack config you need to add or modify the vue.config.js
file that is located in the project root. Add the following to this file:
module.exports = {
chainWebpack: config => {
if (process.env.STORYBOOK && process.env.STORYBOOK.trim() === 'true') {
console.info('info => Updating webpack using chain-webpack')
config.module
.rule('addon-storysource')
.enforce()
.pre()
.test(/\.stories\.jsx?$/)
.use('@storybook/addon-storysource/loader')
.loader('@storybook/addon-storysource/loader')
.options({
semi: false,
printWidth: 120,
singleQuote: true
})
.end()
}
}
}
NOTE We only want the Storysource Addon to kick in when running Storybook so we have to make the rule conditional. To do this we will check if the environment variable STORYBOOK
is set. If it is the rule is added, otherwise it is ignored. We will set the environment variable in the storybook scripts in the package.json
file:
{
"scripts": {
"storybook:build": "SET STORYBOOK=true & vue-cli-service storybook:build -c config/storybook",
"storybook:serve": "SET STORYBOOK=true & vue-cli-service storybook:serve -p 6006 -c config/storybook"
}
}
PRO TIP vue-cli-service
exposes the inspect
command for inspecting the resolved webpack config. The command will print the resolved webpack config to stdout, which also contains hints on how to access rules and plugins via chaining. Add the following entry to the scripts
section in the package.json
file to easily call the inspect
command and redirect the output to the webpack.config.inspect.js
file for easier inspection:
{
"scripts": {
"webpack:inspect": "SET STORYBOOK=true & vue inspect > webpack.config.inspect.js"
}
}
npm install
npm run serve
npm run storybook:serve
npm run build
npm run test
npm run lint
npm run test:unit
-
Make Prettier work with default Vue formatting options. Create a
.prettierrc.js
file in the project root directory with the following contents:module.exports = { semi: false, printWidth: 120, singleQuote: true };
NOTE See Prettier documentation for other options that can be customized.
Run lint to correct formatting:
$ npm run lint
-
Disable the "terminate statement" warning in Webstorm that shows up after changing the Prettier option
semi: false
:- File > Settings... > Editor > Code Style > JavaScript
- Click the
Punctuation
tab - Set
Don't use
semicolon to terminate statementsalways
-
Add an alias for the
tests
directory:Create the
vue.config.js
file and add the following contents, so that the alias is available insrc
:const path = require('path') module.exports = { chainWebpack: config => { config .resolve .alias .set('@tst', path.resolve(__dirname, 'tests')) } }
Add the following entry to the
jest.config.js
file, so that the alias is available to Jest:module.exports = { moduleNameMapper: { '^@tst/(.*)$': '<rootDir>/tests/$1' } }
Currently the following is on the roadmap.
- i18n
- Add storybook-addon-vue-info
- Add actions-addon
- Add backgrounds-addon
- Add chapters-addon
- Add console-addon
- Add info-addon
- Add intl-addon
- Add knobs-addon
- Add links-addon
- Add notes-addon
- Add options-addon
- Add storysource-addon(https://github.com/storybooks/storybook/tree/master/addons/storysource)
- Add viewport-addon
Any other suggestions? Please submit an issue.
We welcome pull requests! What follows is the simplified version of the contribution process, please read here to fully understand our contribution policy and here to understand our code of conduct.
- Fork the repository here!
- Create your feature branch:
git checkout -b my_new_feature
- If relevant, don't forget to add your tests
- Commit your changes:
npm run commit
- Push to the branch:
git push origin my-new-feature
- Submit a pull request :-)
TIP Learn all about forking a repo here. More information on cloning a repo here.
If you like this project, please support us by starring ⭐ this repository. Thx!
Please let the world know about us! Brag about us using Twitter, email, blog, Discord, Slack, forums, etc. etc. Thx!
nidkil © nidkil, released under the MIT license. Authored and maintained by nidkil with help from contributors.