原本公司使用的是使用Typora编写markdown开发文档,再由脚本将md文件转为html文件。这样写的好处是非常简单,但缺点也非常明显
因为公司使用的是angular8+node14,想要在文档的基础上编写开发用例,那不如反过来想,在开发环境上写文档会不会更加方便,由此思路便选择了支持angular的storybook
npm install -g @angular/cli@8
ng new storybook --routing
npx storybook@latest init
"@babel/core": "^7.9.6",
"@compodoc/compodoc": "1.0.2",
"@ngrx/store-devtools": "^9.1.2",
"@storybook/addon-a11y": "5.3.18",
"@storybook/addon-actions": "^5.3.18",
"@storybook/addon-backgrounds": "5.3.18",
"@storybook/addon-centered": "5.3.18",
"@storybook/addon-docs": "^5.3.18",
"@storybook/addon-jest": "5.3.18",
"@storybook/addon-knobs": "5.3.18",
"@storybook/addon-links": "^5.3.18",
"@storybook/addon-notes": "^5.3.18",
"@storybook/addon-options": "5.3.18",
"@storybook/addon-storyshots": "5.3.18",
"@storybook/addon-storysource": "^5.3.18",
"@storybook/addons": "^5.3.18",
"@storybook/angular": "^5.3.18",
"@storybook/source-loader": "5.3.18",
import {Component, ElementRef, Input, OnInit, SimpleChanges} from '@angular/core';
import {JSONEditor} from '@fz/kuafu/jsoneditor.js';
import {actions} from '@storybook/addon-actions';
import {V5Service} from "@fz/v5-core";
@Component({
selector: 'app-button',
templateUrl: './button.component.html',
styleUrls: ['./button.component.scss', '../../common.scss'],
})
export class ButtonComponent implements OnInit {
public editor: JSONEditor;
@Input()
class = 'btn-primary';
@Input()
icon = '';
@Input()
size = [4, 3];
@Input()
disable = false;
editorJson: any = {
schema: {
type: 'object',
title: 'button',
format: 'grid',
options: {},
properties: {
btn1: {
format: 'button',
title: '按钮1',
options: {
size: this.size,
button: {
class: this.class,
icon: this.icon,
action: 'testEvent'
},
}
},
}
}
};
constructor(
private v5: V5Service,
private elementRef: ElementRef
) {
}
ngOnChanges(changes: SimpleChanges): void {
if (!this.editor) {
return;
}
try {
this.editor.destroy();
this.editor = null;
const element = document.getElementById('button');
this.editorJson.schema.properties.btn1.options.size = this.size;
this.editorJson.schema.properties.btn1.options.button.class = this.class;
this.editorJson.schema.properties.btn1.options.button.icon = this.icon;
this.initEvent();
this.initEditor(element, this.editorJson);
if (this.disable) {
this.editor.getEditor('root.btn1').disable();
} else {
this.editor.getEditor('root.btn1').enable();
}
} catch (e) {
console.error(e);
actions(e);
}
}
ngOnInit() {
this.initEvent();
const element = document.getElementById('button');
this.initEditor(element, this.editorJson);
}
initEditor(element: HTMLElement, editorJson: any) {
this.editor = new JSONEditor(element, this.editorJson);
}
private initEvent() {
JSONEditor.defaults.callbacks.button = {
testEvent: (editor, e) => {
this.v5.ui.alert('按钮事件');
}
};
}
}
button.component.html
<div class="row m-2">
<div class="col-12">
<div id="button"></div>
</div>
<div class="col-1"></div>
<div class="code-block col-10 pr-0">
<button ngxClipboard [cbContent]="editorJson| json" *ngIf="editorJson"
class="btn btn-primary btn-sm copy-button"><i class="fa fa-copy"></i> 复制代码
</button>
<code style="white-space: pre-wrap" [highlight]="editorJson | json"
[lineNumbers]="true">
</code>
</div>
</div>
button.stories.ts
import {moduleMetadata, storiesOf} from '@storybook/angular';
import {ButtonComponent} from './button.component';
import {CommonModule} from '@angular/common';
import {array, boolean, text, withKnobs} from '@storybook/addon-knobs';
import {CommonImports, CommonProviders} from '../component.module';
import * as markdown from '../../../assets/docs/button.md';
import {RouterModule} from "@angular/router";
// 获取md文件,没有可以不获取
const stories = storiesOf('Components', module)
.addDecorator(withKnobs) // 配置参数
.addDecorator(
moduleMetadata({
declarations: [ButtonComponent],
imports: [
CommonModule,
RouterModule.forRoot([{path: 'iframe.html', component: ButtonComponent} ]),
...CommonImports,
],
providers: [
...CommonProviders,
],
}),
)
.add('button', () => ({
component: ButtonComponent,
props: {// 增加参数
size: array('size', ['4', '3'], ','),
class: text('class', 'btn-primary'),
icon: text('icon', ''),
disable: boolean('disable', false)
},
}),
{
notes: markdown
}
)
;
component.module.ts(把默认要引入的包放一起 )
import {APP_INITIALIZER, NgModule} from '@angular/core';
import {APP_BASE_HREF, CommonModule, PlatformLocation} from '@angular/common';
import {HttpClientModule, HttpClient} from '@angular/common/http';
import {MarkdownModule} from 'ngx-markdown';
import {HIGHLIGHT_OPTIONS, HighlightJS, HighlightLoader, HighlightModule} from 'ngx-highlightjs';
import {ClipboardModule} from 'ngx-clipboard';
import {PipeModule} from '../pipe/pipe.module';
import {ComponentService} from './component.service';
import {BrowserModule} from '@angular/platform-browser';
import {RouterModule} from '@angular/router';
import {V5CoreModule} from '@fz/v5-core';
import {AppConfigs} from '../configs';
import {Apis} from '../configs/api.config';
const getHighlightLanguages = () => {
return {
typescript: () => import('highlight.js/lib/languages/typescript'),
json: () => import('highlight.js/lib/languages/json'),
xml: () => import('highlight.js/lib/languages/xml')
};
};
export const CommonImports = [
CommonModule,
HttpClientModule,
MarkdownModule.forRoot({
loader: HttpClient,
}
),
HighlightModule,
ClipboardModule,
PipeModule,
BrowserModule,
V5CoreModule.forRoot({
config: AppConfigs,
apis: Apis,
apiDef: {
auth: 'JWT-TOKEN'
},
}),
];
export const CommonProviders = [
{
provide: HIGHLIGHT_OPTIONS,
useValue: {
coreLibraryLoader: () => import('highlight.js/lib/core'),
languages: getHighlightLanguages()
}
},
HighlightJS, // 代码高光
HighlightLoader,
ComponentService,
{
provide: APP_BASE_HREF,
useFactory: () => {
return window.location.href;
},
}
];
@NgModule({
declarations: [],
exports: [],
imports: [],
providers: []
})
export class ComponentModule {
}
::ng-deep .markdown{
body {
font: 400 16px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #111;
background-color: #fdfdfd;
-webkit-text-size-adjust: 100%;
-webkit-font-feature-settings: "kern" 1;
-moz-font-feature-settings: "kern" 1;
-o-font-feature-settings: "kern" 1;
font-feature-settings: "kern" 1;
font-kerning: normal;
}
#main-content {
margin-top: 0px;
}
@media only screen and (max-width: 600px) {
#content {
padding: 0px 20px 20px 20px !important;
}
}
#content {
margin: 0px;
max-width: 900px;
border: 1px solid #e1e4e8;
padding: 30px;
padding-bottom: 40px;
border-radius: 2px;
margin-left: auto;
margin-right: auto;
background-color: white;
}
hr {
color: #bbb;
background-color: #bbb;
height: 1px;
flex: 0 1 auto;
margin: 1em 0;
padding: 0;
border: none;
}
/**
* Links
*/
/* a {
color: #0366d6;
text-decoration: none; }
a:visited {
color: #0366d6; }
a:hover {
color: #0366d6;
text-decoration: underline; } */
pre {
//background-color: #f6f8fa;
border-radius: 3px;
font-size: 100%;
line-height: 1.45;
overflow: auto;
padding: 16px;
}
/**
* Code blocks
*/
code {
background-color: rgba(27, 31, 35, .05);
border-radius: 3px;
font-size: 85%;
margin: 0;
word-wrap: break-word;
padding: .2em .4em;
font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier, monospace;
}
pre>code {
background-color: transparent;
border: 0;
display: inline;
line-height: inherit;
margin: 0;
overflow: visible;
padding: 0;
word-wrap: normal;
font-size: 100%;
}
/**
* Blockquotes
*/
blockquote {
margin-left: 30px;
margin-top: 0px;
margin-bottom: 16px;
border-left-width: 3px;
padding: 0 1em;
color: #828282;
border-left: 4px solid #e8e8e8;
padding-left: 15px;
font-size: 18px;
letter-spacing: -1px;
font-style: italic;
}
blockquote * {
font-style: normal !important;
letter-spacing: 0;
color: #6a737d !important;
}
/**
* Tables
*/
table {
border-spacing: 2px;
display: block;
font-size: 14px;
overflow: auto;
width: 100%;
margin-bottom: 16px;
border-spacing: 0;
border-collapse: collapse;
}
td {
padding: 6px 13px;
border: 1px solid #dfe2e5;
}
th {
font-weight: 600;
padding: 6px 13px;
border: 1px solid #dfe2e5;
}
tr {
background-color: #fff;
border-top: 1px solid #c6cbd1;
}
table tr:nth-child(2n) {
background-color: #f6f8fa;
}
/**
* Others
*/
img {
max-width: 100%;
}
p {
line-height: 24px;
font-weight: 400;
font-size: 16px;
color: #24292e;
}
ul {
margin-top: 0;
}
li {
color: #24292e;
font-size: 16px;
font-weight: 400;
line-height: 1.5;
}
li+li {
margin-top: 0.25em;
}
* {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
color: #24292e;
}
a:visited {
color: #0366d6;
}
h1,
h2,
h3 {
border-bottom: 1px solid #eaecef;
color: #111;
/* Darker */
}
.token.operator{
background-color: unset;
}
}
.code-block {
position: relative;
}
.copy-button {
position: absolute;
top: 0;
right: 0;
}
运行启动npm run storybook
正常运行,https://v5demo.sh-fengze.com/kuafu/docs/index.html?path=/story/*
遇到的坑
8.1 由于是iframe加载组件,所以组件要引入的模块要全部放在stories文件中,如果是service还要创建注入缺少的对象
8.2 想到用请求去获取md文件,但是请求是异步的,storybook是react,写法是函数式组件,传的note参数是字符串,因此只能用.then的链式操作来获取md文件,这样就造成了storybook的异步化,导致加载时外壳会比组件慢,出现第一次加载没有组件的情况,md文件用静态目录去引入就好。
8.3 windows和linux/mac读取静态资源的http路径不一样,windows是不带assets的,linux/mac是带有assets的,需要在angular.json配置只要带有assets/images一律访问images
{
"glob": "**/*",
"input": "src/assets/docs",
"output": "/assets/docs"
},