Storybook是一個輔助UI控件開發的工具。經過story建立獨立的控件,讓每一個控件開發都有一個獨立的開發調試環境。 Storybook的運行不依賴於項目,開發人員不用擔憂因爲開發環境、依賴問題致使不能開發控件。javascript
Storybook支持的框架覆蓋主流的框架(React、Vue、Angular)。 因爲使用React做爲技術棧,本文將介紹使用react的項目如何配置Storybook環境。css
npm i -g storybook
複製代碼
npm i --save-dev @storybook/react
複製代碼
{
"scripts": {
"storybook": "start-storybook -p 9001 -c .storybook"
}
}
複製代碼
在工程根目錄建立.storybook
目錄java
在.storybook
目錄下建立config.js
文件node
import { configure } from '@storybook/react';
import 'index.scss';
function loadStories() {
require('./stories/userStory');
}
configure(loadStories, module);
複製代碼
stories
目錄,但我比較喜歡在.storybook
目錄下建立一個stories
目錄。而後根據不一樣的業務模塊建立不一樣的stories
目錄。 好比有個user模塊,那麼我會建立一個stories/userStory
目錄。// stories/userStory/index.jsx
import React from 'react';
import { storiesOf } from '@storybook/react';
import BasicInfo from 'pages/clientDetail/components/BasicInfo';
storiesOf('用戶信息', module)
.add('基礎信息', () => <BasicInfo />); 複製代碼
至此,根據Storybook React Guide,咱們配置了一個簡單的storybook環境。 其實這個環境已經能夠用了,固然,若是還須要一些額外的功能,好比支持less
和scss
等,就須要自定義webpack配置。react
storybook基礎webpack配置只包含如下幾項:webpack
有時候默認的webpack配置不能知足咱們的項目,所以須要對webpack配置進行擴展。git
一般我使用的是Full Control Mode
對webpack配置進行修改。首先在.storybook
目錄下增長webpack.config.js
文件github
const path = require('path');
module.exports = (storybookBaseConfig, configType) => {
// 如今應該不少項目會使用`less`或者`scss`等css預處理技術。
// 這裏使用了postcss-loader進行處理
storybookBaseConfig.module.rules.push({
test: /\.s?css$/,
use: ['style-loader', { loader: 'css-loader', options: { importLoaders: 1 } }, 'postcss-loader'],
include: path.resolve(__dirname, '../'),
});
return storybookBaseConfig;
};
複製代碼
注意 1:默認配置失效web
若是使用自定義的配置,默認配置就會失效,若是沒有從新配置file-loader
,storybook運行起來時候假若有控件引用了圖片等文件會報錯。chrome
// 默認配置會失效,處理文件須要配置相應的file-loader
storybookBaseConfig.module.rules.push({
test: /\.(gif|png|jpe?g|eot|woff|ttf|pdf)$/,
loader: 'file-loader',
});
複製代碼
注意 2:保留原配置上修改
使用Full Control Mode
模式雖然能夠最大限度修改storybook的webpack
配置,可是如下配置修改時須要注意在原配置上進行擴展。
好比須要添加一個loader,須要像面那樣push一個loader到module.rules
數組中。
storybookBaseConfig.module.rules.push({
test: /\.s?css$/,
use: ['style-loader', { loader: 'css-loader', options: { importLoaders: 1 } }, 'postcss-loader'],
include: path.resolve(__dirname, '../'),
});
複製代碼
其實組件能夠分爲2種,具體能夠參照這篇文章
展現類的控件使用Storybook很簡單,根據展現類的控件傳入props便可。
在個人項目中一般爲react-redux connect後的類。類中操做(如網絡操做)都是經過redux進行的。 爲了讓這個類能正常測試運行,須要進行如下操做。(例子均爲userStory
)
provider
和store
。import { provider } from 'react-redux';
import store from 'store';
複製代碼
story
// stories/userStory/index.jsx
import React from 'react';
import { provider } from 'react-redux'
import { storiesOf } from '@storybook/react';
// 引入store
import store from 'store';
import BasicInfo from 'pages/clientDetail/components/BasicInfo';
storiesOf('用戶信息', module)
.addDecorator(storyFn => <Provider store={store}>{storyFn()}</Provider>)
.add('基礎信息', () => <BasicInfo />);
複製代碼
mock數據
團隊搭建的yapi平臺負責mock數據生成。
whistle
因爲mock數據和storybook不在同一個域,js調用mock數據會跨域,須要作請求轉發代理。咱們團隊使用的是whistle。whistle是個好東西👍,牆裂推薦!!!
如下是whistle配置的轉發規則。
//yourproject.com resCors://*
//localhost:8888 resCors://enable
//yourproject.com http://127.0.0.1:8888/ weinre://
# 9001是storybook的端口
# https://myapi.xxx.com 是yapi所在域名。
^localhost:9001/cgi-bin/** //myapi.xxx.com/mock/3876/cgi-bin/$1
複製代碼
什麼是addons,其實能夠理解擴展storybook功能的插件。
我使用到的addons有
注意
addon-actions和addon-viewport都須要在addons.js
中註冊才能使用。
這個就是個人.storybook
目錄結構。
.
├── README.md
├── addons.js
├── config.js
├── stories
│ └── userStory
│ └── index.js
└── webpack.config.js
複製代碼
config.js
import { configure } from '@storybook/react';
import { setConsoleOptions } from '@storybook/addon-console';
import 'index.scss';
setConsoleOptions({
panelExclude: [],
});
function loadStories() {
require('./stories/userStory');
}
configure(loadStories, module);
複製代碼
webpack.config.js
const path = require('path');
module.exports = (storybookBaseConfig, configType) => {
storybookBaseConfig.module.rules.push({
test: /\.s?css$/,
use: ['style-loader', { loader: 'css-loader', options: { importLoaders: 1 } }, 'postcss-loader'],
include: path.resolve(__dirname, '../'),
});
storybookBaseConfig.module.rules.push({
test: /\.(gif|png|jpe?g|eot|woff|ttf|pdf)$/,
loader: 'file-loader',
});
// 設置別名
storybookBaseConfig.resolve.alias = {
antd: path.resolve(__dirname, '..', `node_modules/antd/dist/antd.min.js`),
antdcss: path.resolve(__dirname, '..', 'node_modules/antd/dist/antd.min.css'),
antdzhCN: path.resolve(__dirname, '..', 'node_modules/antd/lib/locale-provider/zh_CN.js'),
};
// 增長src爲絕對路徑
storybookBaseConfig.resolve.modules.push(path.resolve(__dirname, '..', 'src'));
// 使用source-map
storybookBaseConfig.devtool = 'source-map';
storybookBaseConfig.mode = 'development';
return storybookBaseConfig;
};
複製代碼
addons.js
import '@storybook/addon-actions/register';
import '@storybook/addon-viewport/register';
複製代碼
action和viewport addon均須要在addons.js中註冊才能正常使用。
userStory
import React from 'react';
import { Provider } from 'react-redux';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { withConsole } from '@storybook/addon-console';
import BasicInfo from 'pages/clientDetail/components/BasicInfo';
import KeyActionItem from 'pages/clientDetail/components/KeyAction/KeyActionItem';
import { withInfo } from '@storybook/addon-info';
import store from 'pages/clientDetail/store';
import zhCN from 'antdzhCN';
import { LocaleProvider } from 'antd';
import 'antdcss';
// redux結合
storiesOf('用戶信息', module)
.addDecorator(withInfo)
.addDecorator((storyFn, context) => withConsole()(storyFn)(context))
.addDecorator(storyFn => <Provider store={store}>{storyFn()}</Provider>)
.addDecorator(storyFn => <LocaleProvider locale={zhCN}>{storyFn()}</LocaleProvider>)
.add('基礎信息', () => <BasicInfo />, {
info: {
text: `
用戶基礎信息展現,可進行上下翻頁。
`,
},
});
storiesOf('行爲軌跡item', module)
.addDecorator(withInfo)
.addDecorator((storyFn, context) => withConsole()(storyFn)(context))
.add('行爲軌跡item--課程顧問', () => (
<div style={{ display: 'flex', justifyContent: 'center', paddingTop: '30px' }}>
<KeyActionItem
time="2018-12-12 12:12:12"
user="testUser"
role="課程顧問"
type="saler"
data={[{ title: '備註', content: '備註測試'}]}
id={1}
onClickDelete={action('onClickDelete')}
canDelete
/>
</div>
))
.add('行爲軌跡item--客戶', () => (
<div style={{ display: 'flex', justifyContent: 'center', paddingTop: '30px' }}>
<KeyActionItem
time="2018-12-12 12:12:12"
user="testUser"
role="家長"
type="client"
data={[{ title: '購買記錄', content: '測試購買記錄'}]}
/>
</div>
))
.add('行爲軌跡item--admin', () => (
<div style={{ display: 'flex', justifyContent: 'center', paddingTop: '30px' }}>
<KeyActionItem
time="2018-12-12 12:12:12"
user="admin"
type="admin"
data={[{ title: '購買記錄', content: '測試購買記錄'}]}
/>
</div>
));
複製代碼
我在本文中介紹了最基本的storybook使用。
文章中介紹的使用方式實在是很簡單,但願能安利更多人使用storybook。更多(高大上)storybook的實踐能夠參考這裏 storybook實踐