在不同 webpack 版本的 Vue 项目中配置 Storybook
云前端
共 24564字,需浏览 50分钟
· 2021-04-20
在之前的一篇文章中,介绍过组件化搭建工具 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 || ' '
+ },
+ 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 关注公众号
转载请注明出处
评论
真高!比亚迪员工爆料比亚迪在越南的薪资水平:基本工资480万,全勤奖35万,交通补助20万,餐补110万,每周6天,每天10小时
上一篇:某大公司为逼迫员工离职,竟然把他的工位安排到厕所旁,没想到他直接开始记录领导的如厕时间,还发到公司大群...对此,你怎么看?--完--PS:欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,欢迎转发分享给更多人。全文完,感谢你的耐心阅读。如果你还想看到我的文章,请一定给本
开发者全社区
0
太敢穿了!透视纱裙!性感火辣的身材
绝了呀今天的厂花:吴宣仪1995年1月26日,吴宣仪出生于海南省海口市,中国内地流行乐女歌手、影视演员。2016年2月,吴宣仪随宇宙少女发行首张迷你专辑正式出道。2018年4月,她参加《创造101》综艺选秀,获得第二名,成功加入火箭少女101组合。吴宣仪的颜值一直备受称赞,她的五官立体精致,皮肤白皙
逆锋起笔
0
英伟达Blackwell平台网络配置分析
本文来自“英伟达Blachwell平台网络配置详解”。GTC大会英伟达展示了全新的 Blackwell 平台系列产品,包括 HGX B100 服务器、NVLINK Switch、GB200Superchip Computer Node、Quantum X800 交换机和 CX8 网卡(InfiniB
架构师技术联盟
0
某大公司为逼迫员工离职,竟然把他的工位安排到厕所旁,没想到他直接开始记录领导的如厕时间,还发到公司大群...
上一篇:字节的跳动职级与薪资(2024年)我们与公司间的合作,宛如两艘船只在茫茫大海上相互依靠,共同抵御风浪,携手驶向成功的彼岸。然而,当航向开始产生分歧,或是波涛汹涌的风浪改变了我们的初衷,我们或许应当冷静地选择和平分手,而非在风雨中硬撑。最近,一位网友的遭遇引起了广大职场人的关注和热议。这位网友
开发者全社区
0
金融研究 | 使用Python测量关键审计事项的「信息含量」
Tips: 公众号推送后内容只能更改一次,且只能改20字符。如果内容出问题,或者想更新内容, 只能重复推送。为了更好的阅读体验,建议阅读本文博客版, 链接地址https://textdata.cn/blog/2023-01-13-information-content-of-critical-aud
大邓和他的Python
0
我看阿里的年终奖总算发了!
到4月底了,这两天看朋友圈,发现阿里的年终奖终于发了,问了问老同学,也从网上检索了不少信息,基本搞清楚了阿里今年的年终奖情况。近来来阿里一些集团对绩效等级做了较大的调整,以前的旧绩效系统中,绩效分为3.25、3.5、3.75、4和5五个等级,其中4和5是较高绩效等级,较少见。而且之前3.5绩效内部划
公子龙
0
一女子与一男子在阳台上打扑克,被邻居偷拍后...
近日网络上又发生了一起疑似黄色谣言的事件:一女子与一男子在阳台上打扑克,被邻居偷拍后上传到网上,引发广泛舆论讨论。根据网传视频显示,一名穿着吊带睡衣的女子与一名光着上身的男性在阳台上交谈,随后开始打起扑克牌。这一幕被邻居拍下并上传至网络后,引发了许多网友的关注和猜测,其中大部分涉及到了不当的假设。当
逆锋起笔
0
CVPR 2024|大视觉模型的开山之作!无需任何语言数据即可打造大视觉模型
↑ 点击蓝字 关注极市平台作者丨科技猛兽编辑丨极市平台极市导读 本文提出一种序列建模 (sequential modeling) 的方法,不使用任何语言数据,训练大视觉模型。>>加入极市CV技术交流群,走在计算机视觉的最前沿本文目录1 序列建模打造大视觉模型(来自 U
极市平台
1