vusion-api
Version:
Vusion Node.js API
498 lines (397 loc) • 15.7 kB
text/typescript
import * as fs from 'fs-extra';
import * as path from 'path';
import * as YAML from 'yaml';
import * as utils from '../utils';
/**
* 此 API 为了在 vusion、vue-cli-plugin-vusion 和 @vusion/doc-loader 三者之间共用
* 所以需要提取到 vusion-api 中
*/
function escape(name: string) {
return name.replace(/\\?([[\]<>|])/g, '\\$1');
}
function formatValue(type: string, value: string) {
if (value === null || value === undefined) {
return '';
} else if (Array.isArray(value))
return `\`[${escape(value.join(', '))}]\``;
else if (typeof value === 'string') {
return `\`'${escape(value)}'\``;
} else
return `\`${value}\``;
}
export interface OptionAPI {
name: string;
type: string;
default?: any;
description?: string;
}
export interface AttrAPI {
name: string;
type: string;
options?: Array<any>;
default?: any;
description?: string;
}
export interface ComputedAPI {
name: string;
type: string;
description?: string;
}
export interface DataAPI {
name: string;
type: string;
default?: any;
description?: string;
}
export interface SlotAPI {
name: string;
type: string;
default?: any;
description?: string;
props?: Array<{ name: string, type: string; description?: string }>;
}
export interface EventAPI {
name: string;
description?: string;
params?: Array<{ name: string, type: string; description?: string }>;
}
export interface MethodAPI {
name: string;
type: string;
default?: any;
description?: string;
params?: Array<{ name: string, type: string; default?: string, description?: string }>;
}
export interface AriaAPI {
key: string;
description?: string;
}
export interface ComponentAPI {
name: string;
title?: string;
labels: Array<string>;
description?: string;
docs?: { [name: string]: string };
options?: Array<OptionAPI>;
attrs?: Array<AttrAPI>;
data?: Array<DataAPI>;
computed?: Array<ComputedAPI>;
slots?: Array<SlotAPI>;
events?: Array<EventAPI>;
methods?: Array<MethodAPI>;
aria?: Array<AriaAPI>;
}
export interface VeturTag {
attributes?: Array<string>;
subtags?: Array<string>;
defaults?: Array<string>;
description?: string;
}
export interface VeturAttribute {
version?: string;
type?: string;
options?: Array<string>;
description?: string;
}
export enum MarkdownAPIShowTitle {
'as-needed' = 'as-needed',
'simplified' = 'simplified',
'always' = 'always',
}
export default class APIHandler {
content: string;
json: Array<ComponentAPI>;
// json: ComponentAPI | Array<ComponentAPI>;
fullPath: string;
constructor(content: string = '', fullPath: string) {
this.fullPath = fullPath;
this.content = content;
this.json = this.parse(content);
}
parse(content: string) {
return YAML.parse(content);
}
generate() {
return YAML.stringify(this.json);
}
markdownOptions(options: Array<OptionAPI>) {
const outputs = [];
outputs.push('### Options');
outputs.push('');
outputs.push('| Option | Type | Default | Description |');
outputs.push('| ------ | ---- | ------- | ----------- |');
options.forEach((option) => {
outputs.push(`| ${option.name} | ${escape(option.type)} | ${formatValue(option.type, option.default)} | ${option.description} |`);
});
outputs.push('');
return outputs.join('\n');
}
markdownAttrs(attrs: Array<AttrAPI>) {
const outputs = [];
outputs.push('### Props/Attrs');
outputs.push('');
outputs.push('| Prop/Attr | Type | Options | Default | Description |');
outputs.push('| --------- | ---- | ------- | ------- | ----------- |');
attrs.forEach((attr) => {
outputs.push(`| ${attr.name} | ${escape(attr.type)} | ${attr.options ? attr.options.map((option) => formatValue(attr.type, option)).join(', ') : ''} | ${formatValue(attr.type, attr.default)} | ${attr.description} |`);
});
outputs.push('');
return outputs.join('\n');
}
markdownData(data: Array<DataAPI>) {
const outputs = [];
outputs.push('### Data');
outputs.push('');
outputs.push('| Data | Type | Default | Description |');
outputs.push('| ---- | ---- | ------- | ----------- |');
data.forEach((item) => {
outputs.push(`| ${item.name} | ${escape(item.type)} | ${formatValue(item.type, item.default)} | ${item.description} |`);
});
outputs.push('');
return outputs.join('\n');
}
markdownComputed(computed: Array<ComputedAPI>) {
const outputs = [];
outputs.push('### Computed');
outputs.push('');
outputs.push('| Computed | Type | Description |');
outputs.push('| -------- | ---- | ----------- |');
computed.forEach((item) => {
outputs.push(`| ${item.name} | ${escape(item.type)} | ${item.description} |`);
});
outputs.push('');
return outputs.join('\n');
}
markdownSlots(slots: Array<SlotAPI>) {
const outputs = [];
outputs.push('### Slots');
outputs.push('');
slots.forEach((slot) => {
outputs.push('#### ' + (slot.name === 'default' ? '(default)' : slot.name));
outputs.push('');
outputs.push(slot.description);
outputs.push('');
if (slot.props) {
outputs.push('| Prop | Type | Description |');
outputs.push('| ---- | ---- | ----------- |');
slot.props.forEach((prop) => {
outputs.push(`| ${prop.name} | ${escape(prop.type)} | ${prop.description} |`);
});
outputs.push('');
}
});
return outputs.join('\n');
}
markdownEvents(events: Array<EventAPI>) {
const outputs = [];
outputs.push('### Events');
outputs.push('');
events.forEach((event) => {
outputs.push('#### @' + (event.name));
outputs.push('');
outputs.push(event.description);
outputs.push('');
if (event.params) {
outputs.push('| Param | Type | Description |');
outputs.push('| ----- | ---- | ----------- |');
event.params.forEach((param) => {
outputs.push(`| ${param.name} | ${escape(param.type)} | ${param.description} |`);
});
outputs.push('');
}
});
return outputs.join('\n');
}
markdownMethods(methods: Array<MethodAPI>, type?: string) {
const outputs = [];
outputs.push(`### ${type === 'global' ? 'Global ' : ''}Methods`);
outputs.push('');
methods.forEach((method) => {
outputs.push('#### ' + (method.name));
outputs.push('');
outputs.push(method.description);
outputs.push('');
if (method.params) {
outputs.push('| Param | Type | Default | Description |');
outputs.push('| ----- | ---- | ------- | ----------- |');
method.params.forEach((param) => {
outputs.push(`| ${param.name} | ${escape(param.type)} | ${formatValue(param.type, param.default)} | ${param.description} |`);
});
outputs.push('');
}
});
return outputs.join('\n');
}
markdownARIA(aria: Array<AriaAPI>) {
const outputs = [];
outputs.push('### ARIA and Keyboard');
outputs.push('');
outputs.push('| Key | Description |');
outputs.push('| --- | ----------- |');
aria.forEach((item) => {
outputs.push(`| ${item.key} | ${item.description} |`);
});
outputs.push('');
return outputs.join('\n');
}
markdownAPI(showTitle: MarkdownAPIShowTitle = MarkdownAPIShowTitle['as-needed']) {
const api = this.json;
const outputs: Array<string> = [];
api.forEach(({ name, options, attrs, data, computed, slots, events, methods, aria }) => {
if (showTitle === MarkdownAPIShowTitle['as-needed'])
api.length > 1 && outputs.push(`## ${utils.kebab2Camel(name)} API`);
else if (showTitle === MarkdownAPIShowTitle.simplified)
outputs.push(api.length > 1 ? `## ${utils.kebab2Camel(name)} API` : '## API');
else if (showTitle === MarkdownAPIShowTitle.always)
outputs.push(`## ${utils.kebab2Camel(name)} API`);
if (!(options || attrs || data || computed || slots || events || methods || aria)) {
outputs.push('');
// outputs.push('无');
// outputs.push('');
} else {
options && outputs.push(this.markdownOptions(options));
attrs && outputs.push(this.markdownAttrs(attrs));
data && outputs.push(this.markdownData(data));
computed && outputs.push(this.markdownComputed(computed));
slots && outputs.push(this.markdownSlots(slots));
events && outputs.push(this.markdownEvents(events));
methods && outputs.push(this.markdownMethods(methods));
aria && outputs.push(this.markdownARIA(aria));
}
});
return outputs.join('\n');
};
async markdownIndex() {
const docsDir = path.join(this.fullPath, '../docs');
let docs: Array<string> = [];
if (fs.existsSync(docsDir))
docs = await fs.readdir(docsDir);
const api = this.json;
const outputs: Array<string> = [];
const mainComponent = api[0];
// Title
outputs.push(`<!-- 该 README.md 根据 api.yaml 和 docs/*.md 自动生成,为了方便在 GitHub 和 NPM 上查阅。如需修改,请查看源文件 -->`);
outputs.push('');
outputs.push(`# ${utils.kebab2Camel(mainComponent.name)}${mainComponent.title ? ' ' + mainComponent.title : ''}`);
outputs.push('');
if (mainComponent.labels) {
outputs.push(`<s-component-labels :labels='${JSON.stringify(mainComponent.labels)}'></s-component-labels>`);
outputs.push('');
}
if (mainComponent.description) {
outputs.push(mainComponent.description);
outputs.push('');
}
if (docs.includes('index.md')) {
outputs.push(await fs.readFile(path.join(docsDir, 'index.md'), 'utf8'));
}
outputs.push(`<u-h2-tabs router>`);
if (docs.includes('examples.md'))
outputs.push(`<u-h2-tab title="基础示例" to="examples"></u-h2-tab>`);
if (docs.includes('setup.md'))
outputs.push(`<u-h2-tab title="安装配置" to="setup"></u-h2-tab>`);
if (docs.includes('cases.md'))
outputs.push(`<u-h2-tab title="测试用例" to="cases"></u-h2-tab>`);
if (mainComponent.docs) {
Object.keys(mainComponent.docs).forEach((name) => {
if (docs.includes(name + '.md'))
outputs.push(`<u-h2-tab${name === 'cases' ? ' v-if="NODE_ENV === \'development\'"' : ''} title="${mainComponent.docs[name]}" to="${name}"></u-h2-tab>`);
});
}
if (docs.includes('faq.md'))
outputs.push(`<u-h2-tab title="常见问题" to="faq"></u-h2-tab>`);
outputs.push(`<u-h2-tab title="API" to="api"></u-h2-tab>`);
outputs.push(`</u-h2-tabs>`);
outputs.push('<router-view></router-view>');
return outputs.join('\n');
};
async markdown() {
const docsDir = path.join(this.fullPath, '../docs');
let docs: Array<string> = [];
if (fs.existsSync(docsDir))
docs = await fs.readdir(docsDir);
const api = this.json;
const outputs: Array<string> = [];
const mainComponent = api[0];
// Title
outputs.push(`<!-- 该 README.md 根据 api.yaml 和 docs/*.md 自动生成,为了方便在 GitHub 和 NPM 上查阅。如需修改,请查看源文件 -->`);
outputs.push('');
outputs.push(`# ${utils.kebab2Camel(mainComponent.name)}${mainComponent.title ? ' ' + mainComponent.title : ''}`);
outputs.push('');
if (mainComponent.labels) {
outputs.push(mainComponent.labels.map((label) => `**${label}**`).join(', '));
outputs.push('');
}
if (mainComponent.description) {
outputs.push(mainComponent.description);
outputs.push('');
}
if (docs.includes('index.md')) {
outputs.push(await fs.readFile(path.join(docsDir, 'index.md'), 'utf8'));
}
if (docs.includes('setup.md')) {
outputs.push('## 安装配置');
outputs.push(await fs.readFile(path.join(docsDir, 'setup.md'), 'utf8'));
}
if (docs.includes('examples.md')) {
outputs.push(!mainComponent.docs ? `## 示例` : `## 基础示例`);
outputs.push(await fs.readFile(path.join(docsDir, 'examples.md'), 'utf8'));
}
if (mainComponent.docs) {
const names = Object.keys(mainComponent.docs);
for (const name of names) {
if (docs.includes(name + '.md')) {
outputs.push('## ' + mainComponent.docs[name]);
outputs.push(await fs.readFile(path.join(docsDir, name + '.md'), 'utf8'));
}
}
}
if (docs.includes('faq.md')) {
outputs.push(`## 常见问题`);
outputs.push(await fs.readFile(path.join(docsDir, 'faq.md'), 'utf8'));
}
outputs.push(this.markdownAPI(MarkdownAPIShowTitle.simplified));
return outputs.join('\n');
};
toVetur() {
const api = this.json;
const vetur: {
tags: { [name: string]: VeturTag },
attributes: { [name: string]: VeturAttribute },
} = {
tags: {},
attributes: {},
};
api.forEach((component, index) => {
const veturTag: VeturTag = {
attributes: [],
description: component.description,
}
let hasVModel = false;
if (component.attrs) {
component.attrs.forEach((attr) => {
if (attr.name.startsWith('**'))
return;
const attrName = attr.name.split(/,\s+/g)[0].replace(/\.sync/, '');
if (attr.name.includes('v-model'))
hasVModel = true;
veturTag.attributes.push(attrName);
const veturAttribute: VeturAttribute = {
type: attr.type,
options: attr.options,
description: attr.description,
};
vetur.attributes[`${component.name}/${attrName}`] = veturAttribute;
});
}
if (hasVModel)
veturTag.defaults = ['v-model'];
// @TODO: subsubComponent
if (index === 0 && api.length > 1)
veturTag.subtags = api.slice(1).map((sub) => sub.name);
// @TODO: defaults
vetur.tags[component.name] = veturTag;
});
return vetur;
}
}