专栏首页云前端在不同 webpack 版本的 Vue 项目中配置 Storybook

在不同 webpack 版本的 Vue 项目中配置 Storybook

在之前的一篇文章中,介绍过组件化搭建工具 storybook 在 vue 项目中的安装和配置。相比于其成文的时间,vue 项目依赖的工具多有发展;并且在实际应用中,多种历史版本的项目并存的状况比比皆是,用官方提供的 npx sb init 往往会出现配置失败的情况,而较新或过旧的资料都在网上难觅 -- 所以在此特别补充一篇,记录 新、旧 两种典型配置下,storybook 可用的手动配置方法:

1. babel7 + webpack5

1.1 安装过程

diff --git a/.babelrc b/.babelrc
index e1f4817..71b5fe8 100644
--- a/.babelrc
+++ b/.babelrc
@@ -6,7 +6,8 @@
         "modules": false,
         "targets": "last 2 years, ie > 8"
       }
-    ]
+    ],
+    "@vue/babel-preset-jsx"
   ],
   "plugins": [
     "@babel/plugin-transform-runtime",
diff --git a/.eslintignore b/.eslintignore
index e2192c5..0858135 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -2,4 +2,5 @@
+/test/*
+*.spec.js
diff --git a/.storybook/demo.css b/.storybook/demo.css
new file mode 100644
index 0000000..820f051
--- /dev/null
+++ b/.storybook/demo.css
@@ -0,0 +1,12 @@
+.demo {
+    padding: 50px;
+    display: inline-block;
+}
+.demo .block {
+    margin-bottom: 30px;
+}
diff --git a/.storybook/main.js b/.storybook/main.js
new file mode 100644
index 0000000..ceca416
--- /dev/null
+++ b/.storybook/main.js
@@ -0,0 +1,22 @@
+const path = require("path");
+const pathResolve = p => path.resolve(__dirname, "../", p);
+
+module.exports = {
+  stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
+  addons: ["@storybook/addon-links", "@storybook/addon-essentials"],
+  core: {
+    builder: "webpack5"
+  },
+  webpackFinal: config => {
+    config.resolve.alias = {
+      ...config.resolve.alias,
+      "@": pathResolve("src"),
+      "~": pathResolve("node_modules")
+    };
+    config.module.rules.push({
+      test: /\.scss$/,
+      use: ["vue-style-loader", "css-loader", "sass-loader"]
+    });
+    return config;
+  }
+};
diff --git a/.storybook/manager-head.html b/.storybook/manager-head.html
new file mode 100644
index 0000000..c5c2955
--- /dev/null
+++ b/.storybook/manager-head.html
@@ -0,0 +1,14 @@
+<style>
+  .sidebar-header {
+    text-align: center;
+  }
+  .sidebar-header > div > a {
+    font-size: 28px;
+  }
+</style>
diff --git a/.storybook/manager.js b/.storybook/manager.js
new file mode 100644
index 0000000..c2321a4
--- /dev/null
+++ b/.storybook/manager.js
@@ -0,0 +1,10 @@
+import { addons } from "@storybook/addons";
+import { create } from "@storybook/theming";
+
+addons.setConfig({
+  theme: create({
+    base: "light",
+    brandImage: null,
+    brandTitle: "前端组件"
+  })
+});
diff --git a/.storybook/preview.js b/.storybook/preview.js
new file mode 100644
index 0000000..55d75d7
--- /dev/null
+++ b/.storybook/preview.js
@@ -0,0 +1,23 @@
+import "./demo.css";
+
+export const parameters = {
+    actions: { argTypesRegex: "^on[A-Z].*" },
+    controls: {
+        matchers: {
+            color: /(background|color)$/i,
+            date: /Date$/
+        }
+    },
+    // https://github.com/storybookjs/storybook/pull/9090/commits/2db3faa727fed585fb5d9e6db23cc3835fc88829
+    viewMode: "docs"
+};
diff --git a/package.json b/package.json
index 673f3a9..c0b7b27 100644
--- a/package.json
+++ b/package.json
@@ -1,19 +1,23 @@
+    "storybook": "start-storybook -p 6006 --no-manager-cache",
+    "build-storybook": "build-storybook"
   },
   "dependencies": {
+    "@babel/plugin-syntax-jsx": "^7.12.13",
+    "@storybook/addon-actions": "^6.2.7",
+    "@storybook/addon-essentials": "^6.2.7",
+    "@storybook/addon-links": "^6.2.7",
+    "@storybook/builder-webpack5": "^6.2.7",
+    "@storybook/theming": "^6.2.8",
+    "@storybook/vue": "^6.2.7",
+    "@vue/babel-helper-vue-jsx-merge-props": "^1.2.1",
+    "@vue/babel-preset-jsx": "^1.2.4",
     "babel-plugin-transform-vue-jsx": "~3.7.0",
diff --git a/src/utils/utils.js b/src/utils/utils.js
new file mode 100644
index 0000000..3e8413d
--- /dev/null
+++ b/src/utils/utils.js
@@ -0,0 +1,103 @@
+export const formatCode = jsCode =>
+    JSON.stringify(
+        jsCode,
+        // eslint-disable-next-line
+    (key, value) => {
+            if (typeof value === 'function') {
+                return value.toString();
+            } else {
+                return value;
+            }
+        },
+        2
+    ).replace(/\\n/g, '\n');
diff --git a/src/utils/storyUtils.js b/src/utils/storyUtils.js
new file mode 100644
index 0000000..4ad5149
--- /dev/null
+++ b/src/utils/storyUtils.js
@@ -0,0 +1,49 @@
+import {formatCode} from './utils';
+
+/**
+ * 生成 storybook 用例辅助函数
+ * @param {String} path - 组件的层级式路径,由 `/` 分割
+ * @param {Object} storyComponent - 用于展示的用例
+ * @param {String} markdown - 文档
+ * @param {Object} [originComponent] - 可选,如果需要从原始组件内部的 jsdoc 自动生成 API,则传入原始组件
+ * @return {Object} story - {metadata, named}
+ * @see https://github.com/storybookjs/storybook/issues/8527
+ */
+export const storyOf = (path, storyComponent, markdown, originComponent) => {
+    const pathArr = path.split('/').map(p => {
+    // https://storybook.js.org/docs/vue/writing-stories/args#setting-args-through-the-url
+        if (!/^[0-9a-zA-Z]/.test(p)) {
+            return '#' + p;
+        }
+        return p;
+    });
+    const storyName = pathArr[pathArr.length - 1];
+
+    const metadata = {
+        component: originComponent || void 0,
+        title: pathArr.join('/'),
+        decorators: [
+            () => ({template: '<div class="demo"><story></story></div>'})
+        ]
+    };
+
+    const named = () => storyComponent;
+    named.storyName = storyName;
+    named.parameters = {
+        docs: {
+            description: {
+                component: markdown || '&nbsp;'
+            },
+            source: {
+                // type: 'dynamic'
+                type: 'code',
+                code: formatCode(storyComponent)
+            }
+        }
+    };
+
+    return {
+        metadata,
+        named
+    };
+};

1.2 组件和用例

用例1:

import { Meta, Preview, Description } from '@storybook/addon-docs/blocks'
import readme from './README.md'
import pathPrefix from './pathPrefix.js'

<Meta title={pathPrefix + "/API"} />

<Description markdown={readme} />

组件2:

<template functional>
    <x-table
        v-bind="{ ...data.attrs, ...props }"
        v-on="listeners"
    >
       <!-- ... --> 
    </x-table>
</template>

<script>
export default {
    props: {
    /**
     * 配置中增加了 __自定义 component__ 的能力,避免了在 template 中再分别写 slot;
     * 自定义组件对象会默认接收 `row` 和 `column-config` 两个属性
     */
        columns: {
            type: Array,
            default: () => []
        }
    },
    emits: [
    /**
     * 自定义组件中如果发出同名事件,会被 table 的容器监听到;
     * 参数为 `key, ...values`
     */
        'column-action'
    ]
};
</script>

用例2:

import {storyOf} from '@/utils/storyUtils';
import Comp from '@/components/SimpleTable';

const title = 'Example/SimpleTable|全配置化表格';

const markdown = `
  > 基于 \`x-table\` 的扩展,可以接收其原有属性和事件
`;

const comp = {
    template: `
        <x-simple-table
            :width="600"
            :columns="columns"
            :data="list"
            @column-action="onColAction"
        />
    `,
    data() {
        return {
            //...
        };
    },
    methods: {
        onColAction(act, value) {
            alert([act, value]);
        }
    }
};

const {metadata, named} = storyOf(title, comp, markdown, Comp);
export default metadata;
export const story = named;

2. babel6 + webpack4

2.1 安装过程

diff --git a/.storybook/addons.js b/.storybook/addons.js
new file mode 100644
index 0000000..7106272
--- /dev/null
+++ b/.storybook/addons.js
@@ -0,0 +1,4 @@
+import 'storybook-addon-vue-info/lib/register';
+import '@storybook/addon-backgrounds/register';
+import '@storybook/addon-viewport/register';
+import '@storybook/addon-storysource/register';
diff --git a/.storybook/config.js b/.storybook/config.js
new file mode 100644
index 0000000..f84ef78
--- /dev/null
+++ b/.storybook/config.js
@@ -0,0 +1,34 @@
+import {configure, addDecorator, addParameters} from '@storybook/vue';
+import {create} from '@storybook/theming';
+import {withInfo, setDefaults} from 'storybook-addon-vue-info';
+import './demo.css';
+
+setDefaults({
+    header: false,
+    useDocgen: false
+});
+addDecorator(withInfo);
+addParameters({
+    options: {
+        theme: create({
+            base: 'light',
+            brandImage: null,
+            brandTitle: '前端组件'
+        })
+    }
+});
+const req = require.context('../src', true, /\.stories\.js$/);
+function loadStories() {
+    req.keys().forEach(filename => {
+        try {
+            req(filename);
+        } catch (ex) {
+            console.log('storybook-req', ex);
+        }
+    });
+}
+configure(loadStories, module);
diff --git a/.storybook/manager-head.html b/.storybook/manager-head.html
new file mode 100644
index 0000000..c5c2955
--- /dev/null
+++ b/.storybook/manager-head.html
@@ -0,0 +1,14 @@
+<style>
+  .sidebar-header {
+    text-align: center;
+  }
+  .sidebar-header > div > a {
+    font-size: 28px;
+  }
+</style>
diff --git a/.storybook/demo.css b/.storybook/demo.css
new file mode 100644
index 0000000..820f051
--- /dev/null
+++ b/.storybook/demo.css
@@ -0,0 +1,12 @@
+.demo {
+    padding: 50px;
+    display: inline-block;
+}
+
+.demo .block {
+    margin-bottom: 30px;
+}
diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html
new file mode 100644
index 0000000..e69de29
diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js
new file mode 100644
index 0000000..3186d15
--- /dev/null
+++ b/.storybook/webpack.config.js
@@ -0,0 +1,32 @@
+const path = require('path');
+const pathResolve = p => path.resolve(__dirname, '../', p);
+module.exports = ({config, mode}) => {
+    config.resolve.alias = {
+        ...config.resolve.alias,
+        '@': pathResolve('src'),
+        '~': pathResolve('node_modules')
+    };
+    config.module.rules.push({
+        test: /\.scss$/,
+        use: ['style-loader', 'css-loader', 'sass-loader']
+    });
+    config.module.rules.push({
+        test: /\.vue$/,
+        loader: 'storybook-addon-vue-info/loader',
+        enforce: 'post'
+    });
+    config.module.rules.push({
+        test: /\.stories\.js$/,
+        loaders: [require.resolve('@storybook/addon-storysource/loader')],
+        enforce: 'pre'
+    });
+    if (process.env.NODE_ENV === 'production') {
+        config.output.filename = 'bundle.[name].js';
+        config.optimization.splitChunks.automaticNameDelimiter = '.';
+        config.optimization.runtimeChunk = {
+            name: entrypoint => `runtime.${entrypoint.name}`
+        };
+    }
+    // console.log(config);
+    return config;
+};
diff --git a/package.json b/package.json
index d83bbba..6027c58 100644
--- a/package.json
+++ b/package.json
@@ -21,7 +21,9 @@
+        "build-storybook": "build-storybook",
+        "storybook": "start-storybook -p 6006"
     "devDependencies": {
+        "@storybook/addon-backgrounds": "^5.3.18",
+        "@storybook/addon-info": "^5.3.18",
+        "@storybook/addon-storysource": "^5.3.18",
+        "@storybook/addon-viewport": "^5.3.18",
+        "@storybook/addons": "^5.3.18",
+        "@storybook/theming": "^5.3.18",
+        "@storybook/vue": "^5.3.18",
+        "storybook-addon-vue-info": "^1.4.2",

2.2 用例

import {storiesOf} from '@storybook/vue';
import CustomCols from './index';

const totalColumns = [
    // ...
];

const description = {
    CustomCols: {
        props: {
            choose: '用于 v-model 的值',
            totalColumns: '可选择的列',
            storageName: '本地存储的key',
        },
        events: {
            'on-change': '选择的列改变时触发'
        }
    }
};

const info = {
    useDocgen: false,
    summary: '基于 xxx'
};

storiesOf('增强的自定义表格列', module)
    .add(
        '本机存储上次选择结果',
        () => ({
            components: {
                CustomCols
            },
            template: `
            <div class="demo">
                    <div id="demo-result"></div>
                    <custom-cols
                        :storage-name="selColsStoName"
                        :total-columns="totalColumns"
                        v-model="selectedCols"
                        @on-change="onChange"
                    />
            </div>
            `,
            data() {
                return {
                    totalColumns,
                    selColsStoName: `${window.location.hostname}_overview_custom_cols`,
                    selectedCols: ['impression', 'click']
                };
            },
            methods: {
                onChange(v) {
                    //...
                }
            },
            description
        }),
        {
            info
        }
    );

--End--

本文分享自微信公众号 - 云前端(fewelife),作者:云前端

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-04-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 从零开始,手摸手搭建前端组件库

    https://majunchang.github.io/mi.vant/#/quickStart

    念念不忘
  • 塔荐 | 2018 年最值得关注的 JavaScript 趋势

    前言 JavaScript 渗透的范围越来越广,它能做的事情已经远不止前端开发而已。不久前stateofjs.com刚刚发布了 2017 JavaScript ...

    灯塔大数据
  • 手摸手教你用 Storybook 改善组件库的开发

    在上一篇文章 《手摸手教你封装跨项目复用的 Vue 组件》 中,介绍了一例用 rollup.js 封装 Vue.js 组件库的实践;限于篇幅和复杂度,其中组件的...

    江米小枣
  • 一天一夜,山月写完了这份高效组织 npm script 最佳实践

    众所周知,一个 Javasript 项目的脚本类工具,可以使用 package.json 中的 scripts 字段来组织,简单来说,这就是 npm scrip...

    前端劝退师
  • vue 开发常用工具及配置二

    作者在个人工作中喜欢用go语言,因为它简单方便。先安装go语言环境,然后于终端执行如下命令:

    程序员LIYI
  • 【译】使用 Webpack 和 Poi 构建更好的 JavaScript 应用

    Webpack 是一个将你所有的 JavaScript 文件,图片、字库以及 CSS 等打包到一个依赖关系图的构建工具。这使得你可以在源码中使用 require...

    逆葵
  • React v16 与 Webpack v4 的多特性模板项目

    React & Webpack Boilerplate V4 是笔者前端常用模板集锦项目 fe-boilerplate 的一部分,尽可能地使用无异议的工具,提供...

    王下邀月熊
  • 十款值得你关注的Vue.js工具和库

    众所周知,Vue目前越来越热门,被很多开发人员采用,因此其生态也越来越完善,相关的工具和库也很丰富。这主要得益于:Vue的学习曲线,清晰的设计结构和使用文档,让...

    前端达人
  • storybook的介绍和使用 比较火的响应式UI开发及测试环境

    storybook是一套最近比较火的响应式UI 开发及测试环境。 可以可视化开发调试react,vue组件 官网:https://github.com/st...

    mafeifan
  • 玩转 Angular 环境变量

    对于 “程序猿” 来说,在日常开发过程中,我们经常要与不同的开发环境打交道。在实际的项目的开发过程中,一般至少会有两个环境:开发环境和线上环境,这是很简单的情形...

    阿宝哥
  • 2018 年前端开发五大趋势

    对于开发者而言,2017出奇的高效,不过2018年有望为IT领域带来更多发展。本文中,为了处理项目时紧跟潮流,我们会描述出每个前端编程人都需要关注的2018年J...

    我就是马云飞
  • 【组件库封装】:封装一个 Library 什么流程?

    在项目开发初期,我们通常会选用主流的UI开发框架来构建应用(例如:基于 React 的 AntDesign、基于 Vue 的 ElementUI)。随着业务研发...

    WEBJ2EE
  • vue开发工具有哪些,那个更合适?

    现在前端除了JavaScript外,还有react,vue,angular这三个框架在市场上用的比较多,可以说这三个框架很大程度上改变了前端的地位,相对于ang...

    北京锐智互动
  • ? 对比三个强大的组件文档展示工具

    我们项目里面也尝试了几种不同的文档工具,今天和大家分享一些经验, 希望对大家有所帮助。

    皮小蛋
  • 【webpack】从vue-cli 2x 到 3x 迁移与实践

    左图项目结构为vue-cli 2x版本脚手架生成的项目,build文件夹包含了webpack配置

    树酱
  • Vue CLI 2.x搭建Vue项目

    windows  1. Node.js (>=6.x, 推荐8.x) Node官网下载 .msi 文件,按步骤下载安装即可。   安装完之后在cmd中输入 ...

    文渊同学
  • Vue 3 和 Webpack 5 来了,手动搭建的知识该更新了

    长此以往,会导致你对整个项目的把控度越来越低。面试下一家公司的面试官问你,是否手动搭建过 Vue 项目的时候,对配置一问三不知? 。本文着重为大家讲解从 0 到...

    Nealyang
  • 【webpack】从vue-cli 2x 到 3x 迁移与实践

    左图项目结构为vue-cli 2x版本脚手架生成的项目,build文件夹包含了webpack配置

    树酱
  • Vue-cli教程

    一、安装vue-cli 安装vue-cli的前提是你已经安装了npm,安装npm你可以直接下载node的安装包进行安装。你可以在命令行工具里输入npm -v  ...

    用户1212940

扫码关注云+社区

领取腾讯云代金券