storybook在vue中搭建可视化组件库

2020-02-15 loading

# storybook官方文档

# storybook安装步骤

npm install @storybook/vue --save-dev

Make sure that you have vue, vue-loader, vue-template-compiler, @babel/core, babel-loader and babel-preset-vue in your dependencies as well, because we list these as a peer dependencies:

npm install vue --save
npm install vue-loader vue-template-compiler @babel/core babel-loader babel-preset-vue --save-dev 

配置package.json

{
  "scripts": {
    "storybook": "start-storybook"
  }
}

# 1.配置端口 指定配置文件,配置文件需要是文件夹内部的config.js

"storybook": "start-storybook -p 9001 -c .storybook"

  • -c后面的.storybook为指定的配置目录,可以自定义文件名
  • -p后面配置访问的端口

# 2.将组建打包成静态页面,可以单独访问

"build-storybook": "build-storybook -c .storybook -o .storybook.out"
  • -c后面的.storybook为指定的配置目录,可以自定义文件名
  • -o后面配置打包完成的静态文件输出文件夹

# 3.配置文件 .storybook/config.js

import {
  configure,
  addParameters,
  addDecorator
} from '@storybook/vue';
import {
  INITIAL_VIEWPORTS
} from '@storybook/addon-viewport';
// import '@storybook/addon-console';
import {
  setConsoleOptions
} from '@storybook/addon-console';

// addDecorator((storyFn, context) => withConsole()(storyFn)(context));
setConsoleOptions({
  options: {
    hierarchyRootSeparator: /\|/, //标题分类
  },
  panelExclude: [],
});

const newViewports = {
  kindleFire2: {
    name: 'Kindle Fire 2',
    styles: {
      width: '600px',
      height: '963px',
    },
  },
  kindleFireHD: {
    name: 'Kindle Fire HD',
    styles: {
      width: '533px',
      height: '801px',
    },
  },
};
addParameters({
  //配置背景色选项
  backgrounds: [{
      name: 'white',
      value: 'white',
      default: true
    },
    {
      name: 'facebook',
      value: '#3b5998'
    },
    {
      name: 'yellow',
      value: 'yellow'
    },
  ],
  //自定义视图大小
  viewport: {
    viewports: {
      ...INITIAL_VIEWPORTS,
      ...newViewports
    },
  }
});

function importAll(r) {
  r.keys().forEach(r)
}

//待处理的 storybook文件
function loadStories() {
  importAll(require.context("../stories", true, /\.story\.js$/));
  importAll(require.context("../src/components", true, /\.story\.js$/));
}

configure(loadStories, module);

# 4.引入插件 .storybook/addons.js ,插件引入顺序决定了 界面展示顺序

import '@storybook/addon-notes/register'; //添加备注
import '@storybook/addon-storysource/register';//显示源码
import '@storybook/addon-knobs/register';//显示props字段并可以更改
import '@storybook/addon-backgrounds/register';//设置背景色
import '@storybook/addon-viewport/register';//设置自定义屏幕
import '@storybook/addon-actions/register';
import '@storybook/addon-links/register';

# 5.组件写法示例

import {
  storiesOf
} from '@storybook/vue';

import {
  linkTo
} from '@storybook/addon-links';

import MyButton from './Button.vue';

import {
  withKnobs,
  text,
  number,
  boolean,
  array,
  select,
  color,
  date,
  button
} from '@storybook/addon-knobs';

import Vue from 'vue';
Vue.component('MyButton', MyButton);


storiesOf('Addon|Notes', module)
  .add('Default', () => ({
    template: '<MyButton>addon notes</MyButton>'
  }), {
    notes: 'You can write anything about this component'
  })

storiesOf('Addon|Links', module)
  .add('Go to Compents|Button', () => ({
    template: `<MyButton :rounded="true" @click-btn="click">addon Links</MyButton>`,
    methods: {
      click(val){
        console.log('click---'+val)
        linkTo('Compents|Button','custom button')
      } 
    },
  }))

storiesOf('Addon|Backgrounds', module)
  .add('Default', () => ({
    template: '<MyButton>addon backgrounds</MyButton>'
  }), {
    notes: 'with emoji-----backgrounds',
    backgrounds: [{
      name: 'grey',
      value: '#eeeeee',
      default: true
    }]
  })

storiesOf('Addon|Viewport', module)
  .add('Default', () => ({
    template: '<MyButton :rounded="true">addon viewport</MyButton>'
  }), {
    viewport: {
      defaultViewport: 'iphonex'
    }
  })


storiesOf('Addon|Knobs', module)
  .addDecorator(withKnobs)
  .add('All knobs', () => {
    const fruits = {
      Apple: 'apples',
      Banana: 'bananas',
      Cherry: 'cherries',
    };

    button('Arbitrary action', 'You clicked it!');

    return {
      props: {
        name: {
          default: text('Name', 'Jane')
        },
        stock: {
          default: number('Stock', 20, {
            range: true,
            min: 0,
            max: 30,
            step: 5,
          }),
        },
        fruit: {
          default: select('Fruit', fruits, 'apples')
        },
        price: {
          default: number('Price', 2.25)
        },
        colour: {
          default: color('Border', 'deeppink')
        },
        today: {
          default: date('Today', new Date('Jan 20 2017 GMT+0'))
        },
        // this is necessary, because we cant use arrays/objects directly in vue prop default values
        // a factory function is required, but we need to make sure the knob is only called once
        items: {
          default: (items => () => items)(array('Items', ['Laptop', 'Book', 'Whiskey']))
        },
        nice: {
          default: boolean('Nice', true)
        },
      },
      data: () => ({
        dateOptions: {
          year: 'numeric',
          month: 'long',
          day: 'numeric',
          timeZone: 'UTC'
        },
      }),
      computed: {
        stockMessage() {
          return this.stock ?
            `I have a stock of ${this.stock} ${this.fruit}, costing $${this.price} each.` :
            `I'm out of ${this.fruit}${this.nice ? ', Sorry!' : '.'}`;
        },
        salutation() {
          return this.nice ? 'Nice to meet you!' : 'Leave me alone!';
        },
        formattedDate() {
          return new Date(this.today).toLocaleDateString('en-US', this.dateOptions);
        },
        style() {
          return {
            'border-color': this.colour,
          };
        },
      },
      template: `
          <div style="border: 2px dotted; padding: 8px 22px; border-radius: 8px" :style="style">
            <h1>My name is {{ name }},</h1>
            <h3>today is {{ formattedDate }}</h3>
            <p>{{ stockMessage }}</p>
            <p>Also, I have:</p>
            <ul>
              <li v-for="item in items" :key="item">{{ item }}</li>
            </ul>
            <p>{{ salutation }}</p>
          </div>
        `,
    };
  });
  • storiesOf()第一个参数是该组件文件夹的名称,名称如果由2个单词组成,并且中间以|分割,则第一个单词成为外层分类
  • .add()第一个参数是当前该storybook的文件名,同一个storiesOf下的文件名不能重复
  • 写法跟采用的框架写法一致

# 6.自定义打包编译配置

Create a .storybook/webpack.config.js file. 查看默认的webpack配置

Edit it’s contents:
module.exports = async ({ config }) => console.dir(config.plugins, { depth: null }) || config;

Then run storybook:即可查看默认的webpack配置

yarn storybook --debug-webpack

增加less

const path = require('path');

// Export a function. Accept the base config as the only param.
module.exports = async ({
  config,
  mode
}) => {
  // `mode` 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$/,
    use: ['style-loader', 'css-loader', 'postcss-loader', 'less-loader'],
    include: path.resolve(__dirname, '../'),
  });
  //配置查看代码源码
  config.module.rules.push({
    test: /\.story\.jsx?$/,
    loaders: [require.resolve('@storybook/addon-storysource/loader')],
    include: [path.resolve(__dirname, '../src'), path.resolve(__dirname, '../stories')],
    enforce: 'pre',
  });

  // Return the altered config
  return config;
};

# storybook一键安装

mkdir storybook-vue
cd storybook-vue/
npm init -y
npx -p @storybook/cli sb init --type vue
npm run storybook

需要安装部分缺失插件

npm i -D vue-loader @storybook/addon-storysource
npm i vue