vusion-api
Version:
Vusion Node.js API
622 lines • 27.5 kB
JavaScript
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.APIShowTitle = void 0;
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const YAML = __importStar(require("yaml"));
const utils = __importStar(require("../utils"));
const MarkdownIt = require("markdown-it");
const uslug = require("uslug");
const uslugify = (s) => uslug(s);
/**
* 此 API 为了在 vusion、vue-cli-plugin-vusion 和 @vusion/doc-loader 三者之间共用
* 所以需要提取到 vusion-api 中
*/
function escape(name = '') {
if (typeof name !== 'string')
name = String(name);
return name.replace(/\\?([[\]<>|])/g, '\\$1');
}
function formatValue(value) {
if (value === null || value === undefined) {
return '';
}
else if (Array.isArray(value))
return `[${(value.map((valItem) => {
return formatValue(valItem);
}).join(','))}]`;
else if (Object.prototype.toString.call(value) === '[object Object]') {
const codeItems = [];
for (const key in value) {
codeItems.push(`${key}: ${formatValue(value[key])}`);
}
return `{${codeItems.join(',')}}`;
}
else if (typeof value === 'string') {
return `\`'${escape(value)}'\``;
}
else if (value.name) {
return `\`'${escape(value.name)}'\` (${escape(value.description)})`;
}
else
return `\`${value}\``;
}
/**
* 如何显示组件二级标题
*/
var APIShowTitle;
(function (APIShowTitle) {
/**
* 需要时显示
* 在有子组件的情况下才显示每个组件的完全标题
*/
APIShowTitle["as-needed"] = "as-needed";
/**
* 简化显示
* 在有子组件的情况下显示每个组件的完全标题,在没有的时候只显示`API`的字样
*/
APIShowTitle["simplified"] = "simplified";
/**
* 总是显示完整标题
*/
APIShowTitle["always"] = "always";
})(APIShowTitle = exports.APIShowTitle || (exports.APIShowTitle = {}));
/**
* 处理组件 API 的类
* 用于修改、保存,以及生成 Markdown 文档等功能
*/
class APIHandler {
constructor(content = '', fullPath) {
this.fullPath = fullPath;
this.content = content;
this.json = this.parse(content);
this.markdownIt = new MarkdownIt({
html: true,
langPrefix: 'lang-',
});
}
/**
* 解析 api.yaml 的内容
* @param content api.yaml 的文件内容
*/
parse(content) {
try {
return YAML.parse(content);
}
catch (e) {
console.error(this.fullPath);
console.error(e);
}
}
/**
* 将处理类中的 json 对象重新转回 YAML 文件
*/
generate() {
return YAML.stringify(this.json);
}
/**
* 将 API 中的 options 列表转换成 Markdown
* @param options
*/
markdownOptions(options) {
const outputs = [];
outputs.push('### ' + "Options" /* ComponentAPISubtitle.options */);
outputs.push('');
outputs.push('| Option | Type | Default | Description |');
outputs.push('| ------ | ---- | ------- | ----------- |');
options.forEach((option) => {
outputs.push(`| ${option.name} | ${escape(option.type)} | ${formatValue(option.default)} | ${option.description} |`);
});
outputs.push('');
return outputs.join('\n');
}
/**
* 将 API 中的 attrs 列表转换成 Markdown
* @param attrs
*/
markdownAttrs(attrs) {
const outputs = [];
outputs.push('### ' + "Props/Attrs" /* ComponentAPISubtitle.attrs */);
outputs.push('');
outputs.push('| Prop/Attr | Type | Options | Default | Description |');
outputs.push('| --------- | ---- | ------- | ------- | ----------- |');
attrs.forEach((attr) => {
let name = attr.name;
if (attr.sync)
name += '.sync';
if (attr.model)
name += ', v-model';
outputs.push(`| ${name} | ${escape(attr.type)} | ${attr.options ? attr.options.map((option) => formatValue(option)).join('<br/>') : ''} | ${formatValue(attr.default)} | ${escape(attr.description)} |`);
});
outputs.push('');
return outputs.join('\n');
}
/**
* 将 API 中的 data 列表转换成 Markdown
* @param data
*/
markdownData(data) {
const outputs = [];
outputs.push('### ' + "Data" /* ComponentAPISubtitle.data */);
outputs.push('');
outputs.push('| Data | Type | Default | Description |');
outputs.push('| ---- | ---- | ------- | ----------- |');
data.forEach((item) => {
outputs.push(`| ${item.name} | ${escape(item.type)} | ${formatValue(item.default)} | ${item.description} |`);
});
outputs.push('');
return outputs.join('\n');
}
/**
* 将 API 中的 computed 列表转换成 Markdown
* @param computed
*/
markdownComputed(computed) {
const outputs = [];
outputs.push('### ' + "Computed" /* ComponentAPISubtitle.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');
}
/**
* 将 API 中的 slots 列表转换成 Markdown
* @param slots
*/
markdownSlots(slots) {
const outputs = [];
outputs.push('### ' + "Slots" /* ComponentAPISubtitle.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');
}
/**
* 将 API 中的 events 列表转换成 Markdown
* @param events
*/
markdownEvents(events) {
const outputs = [];
outputs.push('### ' + "Events" /* ComponentAPISubtitle.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');
}
/**
* 将 API 中的 methods 列表转换成 Markdown
* @param methods
*/
markdownMethods(methods, type) {
const outputs = [];
outputs.push('### ' + type === 'global' ? "Global Methods" /* ComponentAPISubtitle.globalMethods */ : "Methods" /* ComponentAPISubtitle.methods */);
outputs.push('');
methods.forEach((method) => {
let methodName = method.name;
if (!methodName.includes('(')) {
methodName = `${method.name}(${(method.params || []).map((param) => param.name).join(', ')})`;
}
outputs.push('#### ' + methodName);
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.default)} | ${param.description} |`);
});
outputs.push('');
}
});
return outputs.join('\n');
}
/**
* 将 API 中的 aria 列表转换成 Markdown
* @param aria
*/
markdownARIA(aria) {
const outputs = [];
outputs.push('### ' + "ARIA and Keyboard" /* ComponentAPISubtitle.aria */);
outputs.push('');
outputs.push('| Key | Description |');
outputs.push('| --- | ----------- |');
aria.forEach((item) => {
outputs.push(`| <kdb>${item.key}</kdb> | ${item.description} |`);
});
outputs.push('');
return outputs.join('\n');
}
/**
* 将组件 API 转换成 Markdown
* @param showTitle 如何显示组件二级标题
*/
markdownAPI(showTitle = APIShowTitle['as-needed']) {
const api = this.json;
const outputs = [];
api.forEach(({ name, options, attrs, data, computed, slots, events, methods, aria }) => {
if (showTitle === APIShowTitle['as-needed'])
api.length > 1 && outputs.push(`## ${utils.kebab2Camel(name)} API`);
else if (showTitle === APIShowTitle.simplified)
outputs.push(api.length > 1 ? `## ${utils.kebab2Camel(name)} API` : '## API');
else if (showTitle === APIShowTitle.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');
}
;
getTOCFromAPI(showTitle = APIShowTitle['simplified']) {
const api = this.json;
const tocLinks = [];
api.forEach(({ name, options, attrs, data, computed, slots, events, methods, aria }) => {
// if (showTitle === APIShowTitle['as-needed']) {
// const title = `${utils.kebab2Camel(name)} API`;
// api.length > 1 && tocLinks.push({ title, to: { hash: '#' + uslugify(title) }, children: [] });
// }
let parentLink;
if (showTitle === APIShowTitle.simplified) {
const title = api.length > 1 ? `${utils.kebab2Camel(name)} API` : 'API';
parentLink = { title, to: { path: 'api', hash: api.length > 1 ? '#' + uslugify(title) : '' }, children: [] };
tocLinks.push(parentLink);
}
else if (showTitle === APIShowTitle.always) {
const title = `## ${utils.kebab2Camel(name)} API`;
parentLink = { title, to: { path: 'api', hash: '#' + uslugify(title) }, children: [] };
tocLinks.push(parentLink);
}
options && parentLink.children.push({ title: "Options" /* ComponentAPISubtitle.options */, to: { path: 'api', hash: '#' + uslugify("Options" /* ComponentAPISubtitle.options */) } });
attrs && parentLink.children.push({ title: "Props/Attrs" /* ComponentAPISubtitle.attrs */, to: { path: 'api', hash: '#' + uslugify("Props/Attrs" /* ComponentAPISubtitle.attrs */) } });
data && parentLink.children.push({ title: "Data" /* ComponentAPISubtitle.data */, to: { path: 'api', hash: '#' + uslugify("Data" /* ComponentAPISubtitle.data */) } });
computed && parentLink.children.push({ title: "Computed" /* ComponentAPISubtitle.computed */, to: { path: 'api', hash: '#' + uslugify("Computed" /* ComponentAPISubtitle.computed */) } });
slots && parentLink.children.push({ title: "Slots" /* ComponentAPISubtitle.slots */, to: { path: 'api', hash: '#' + uslugify("Slots" /* ComponentAPISubtitle.slots */) } });
events && parentLink.children.push({ title: "Events" /* ComponentAPISubtitle.events */, to: { path: 'api', hash: '#' + uslugify("Events" /* ComponentAPISubtitle.events */) } });
methods && parentLink.children.push({ title: "Methods" /* ComponentAPISubtitle.methods */, to: { path: 'api', hash: '#' + uslugify("Methods" /* ComponentAPISubtitle.methods */) } });
aria && parentLink.children.push({ title: "ARIA and Keyboard" /* ComponentAPISubtitle.aria */, to: { path: 'api', hash: '#' + uslugify("ARIA and Keyboard" /* ComponentAPISubtitle.aria */) } });
});
return tocLinks;
}
getTOCFromContent(content, to, options = { maxLevel: 3, minLevel: 3 }) {
const tocLinks = [];
const tokens = this.markdownIt.parse(content, {});
tokens.forEach((token, index) => {
if (token.type !== 'heading_close')
return;
const inline = tokens[index - 1];
if (!(inline && inline.type === 'inline'))
return;
let level = +token.tag.slice(1);
if (level < options.maxLevel || level > options.minLevel)
return;
const title = inline.content.trim();
const link = { title, to: { path: to, hash: '#' + uslugify(title) } };
let parentLink = { title: '', children: tocLinks };
while (parentLink && level > options.maxLevel) {
parentLink = parentLink.children[parentLink.children.length - 1];
parentLink && (parentLink.children = parentLink.children || []);
level--;
}
parentLink && parentLink.children.push(link);
});
return tocLinks;
}
getTOCFromFile(fullPath, to, options = { maxLevel: 3, minLevel: 3 }) {
return __awaiter(this, void 0, void 0, function* () {
const content = yield fs.readFile(fullPath, 'utf8');
return this.getTOCFromContent(content, to, options);
});
}
/**
* 由目录链接树转换成 Markdown
* 添加 link 时的去重操作不太方便,所以在这里操作
*/
markdownTOC(tocLinks, vue = false, level = 0, toHashMap = new Map()) {
const indent = (l) => ' '.repeat(l * 4);
const unique = ({ path, hash }) => {
let uniq = `${path}${hash}`;
let i = 2;
while (toHashMap.has(uniq))
uniq = `${path}${hash}-${i++}`;
toHashMap.set(uniq, true);
return uniq.slice(String(path).length);
};
const outputs = [];
if (vue) {
level === 0 && outputs.push(`<u-toc>`);
tocLinks.forEach((link) => {
if (typeof link.to === 'object')
link.to.hash = unique(link.to);
const start = indent(level + 1) + `<u-toc-item${link.development ? ' v-if="NODE_ENV === \'development\'"' : ''} label="${link.title}" ${typeof link.to === 'object' ? ':to=\'' + JSON.stringify(link.to) + '\'' : 'to="' + link.to + '"'}>`;
if (link.children && link.children.length) {
outputs.push(start);
outputs.push(this.markdownTOC(link.children, vue, level + 1, toHashMap));
outputs.push(indent(level + 1) + '</u-toc-item>');
}
else {
outputs.push(start + '</u-toc-item>');
}
});
level === 0 && outputs.push(indent(level) + '</u-toc>');
}
else {
tocLinks.forEach((link) => {
if (typeof link.to === 'object')
link.to.hash = unique(link.to);
outputs.push(indent(level) + `- [${link.title}](${typeof link.to === 'object' ? link.to.hash : '#' + link.to})`);
link.children && outputs.push(this.markdownTOC(link.children, vue, level + 1, toHashMap));
});
}
return outputs.join('\n');
}
/**
* 生成多页面组件的顶级页面
* 会读取组件目录下的 docs 子文档
*/
markdownIndex() {
return __awaiter(this, void 0, void 0, function* () {
const docsDir = path.join(this.fullPath, '../docs');
let docs = [];
if (fs.existsSync(docsDir))
docs = yield fs.readdir(docsDir);
const api = this.json;
/**
* 最终 Markdown 输出
*/
const outputs = [];
/**
* 生成文档目录
* 从二级标题开始
*/
const tocRoot = [];
const addSubdoc = (fileName, title, to, development = false) => __awaiter(this, void 0, void 0, function* () {
const tocLinks = yield this.getTOCFromFile(path.resolve(docsDir, fileName), to);
const link = { title, to, development, children: tocLinks };
tocRoot.push(link);
outputs.push(` <u-h2-tab${development ? ' v-if="NODE_ENV === \'development\'"' : ''} title="${title}" to="${to}"></u-h2-tab>`);
});
/**
* API 中的主组件
*/
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')) {
const indexPath = path.join(docsDir, 'index.md');
const indexContent = yield fs.readFile(indexPath, 'utf8');
outputs.push(indexContent);
const tocLinks = yield this.getTOCFromContent(indexContent);
tocLinks.length && tocRoot.push({ title: '概述', children: tocLinks });
}
outputs.push(`<u-h2-tabs router>`);
if (docs.includes('examples.md'))
yield addSubdoc('examples.md', '基础示例', 'examples');
if (docs.includes('setup.md'))
yield addSubdoc('setup.md', '安装配置', 'setup');
if (mainComponent.docs) {
const names = Object.keys(mainComponent.docs);
for (const name of names) {
if (docs.includes(name + '.md'))
yield addSubdoc(name + '.md', mainComponent.docs[name], name);
}
}
if (docs.includes('blocks.md'))
yield addSubdoc('blocks.md', '内置区块', 'blocks', true);
if (docs.includes('cases.md'))
yield addSubdoc('cases.md', '测试用例', 'cases', true);
if (docs.includes('faq.md'))
yield addSubdoc('faq.md', '常见问题', 'faq');
{
const link = { title: 'API', to: 'api' };
tocRoot.push(...this.getTOCFromAPI());
outputs.push(` <u-h2-tab title="${link.title}" to="${link.to}"></u-h2-tab>`);
}
const changelogPath = path.resolve(this.fullPath, '../CHANGELOG.md');
if (fs.existsSync(changelogPath)) {
const link = { title: '更新日志', to: 'changelog' };
tocRoot.push(link);
outputs.push(` <u-h2-tab title="${link.title}" to="${link.to}"></u-h2-tab>`);
}
outputs.push(`</u-h2-tabs>`);
outputs.push('<router-view></router-view>');
// 插入目录
outputs.splice(4, 0, this.markdownTOC(tocRoot, true), '');
return outputs.join('\n') + '\n';
});
}
;
markdown() {
return __awaiter(this, void 0, void 0, function* () {
const docsDir = path.join(this.fullPath, '../docs');
let docs = [];
if (fs.existsSync(docsDir))
docs = yield fs.readdir(docsDir);
const api = this.json;
/**
* 最终 Markdown 输出
*/
const outputs = [];
/**
* 生成文档目录
* 从二级标题开始
*/
const tocRoot = [];
const addSubdoc = (fileName, title) => __awaiter(this, void 0, void 0, function* () {
const filePath = path.resolve(docsDir, fileName);
const content = yield fs.readFile(filePath, 'utf8');
outputs.push('## ' + title);
outputs.push(content);
const tocLinks = this.getTOCFromContent(content);
const link = { title, to: uslugify(title), children: tocLinks };
tocRoot.push(link);
});
/**
* API 中的主组件
*/
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')) {
const indexPath = path.join(docsDir, 'index.md');
const indexContent = yield fs.readFile(indexPath, 'utf8');
outputs.push(indexContent);
const tocLinks = yield this.getTOCFromContent(indexContent);
tocLinks.length && tocRoot.push({ title: '概述', children: tocLinks });
}
if (docs.includes('setup.md'))
yield addSubdoc('setup.md', '安装配置');
if (docs.includes('examples.md'))
yield addSubdoc('examples.md', !mainComponent.docs ? '示例' : '基础示例');
if (mainComponent.docs) {
const names = Object.keys(mainComponent.docs);
for (const name of names) {
if (docs.includes(name + '.md'))
yield addSubdoc(name + '.md', mainComponent.docs[name]);
}
}
if (docs.includes('faq.md'))
yield addSubdoc('faq.md', '常见问题');
outputs.push(this.markdownAPI(APIShowTitle.simplified));
tocRoot.push(...this.getTOCFromAPI());
// 插入目录
outputs.splice(4, 0, this.markdownTOC(tocRoot), '');
return outputs.join('\n') + '\n';
});
}
;
toVetur() {
const api = this.json;
const vetur = {
tags: {},
attributes: {},
};
api.forEach((component, index) => {
const 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 = {
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;
}
}
exports.default = APIHandler;
//# sourceMappingURL=APIHandler.js.map
;