UNPKG

vusion-api

Version:
498 lines (397 loc) 15.7 kB
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; } }