предисловие
Когда мы используем библиотеку компонентов, страница документации является самым прямым окном для получения информации. Страница документации обычно содержит следующую информацию:
- описание компонента
- Демо-шоу Примерные компоненты и исходный код Описание
- Документация по параметрам для компонентов
Для отладки и отображения чисто компонентных примеров мы, конечно, можем выбрать такие инструменты, как storybook, но, учитывая красоту и сложность восстановления по схеме проекта, нам все равно придется подумать о написании самостоятельно настраиваемого и расширяемого инструмента.
анализировать
Давайте разберем наши требования с информацией, содержащейся на странице документации выше:
- Самый лаконичный синтаксис для написания страниц
- Самый лаконичный синтаксис для демонстрации Демо + исходный код + описание примера
- Документирование параметров обслуживания с минимальными затратами
С точки зрения синтаксиса мы должны предпочесть уценку, синтаксис лаконичный и достаточно мощный.
Чтобы отобразить демо и исходный код, чтобы поддерживать более эффективный и недорогой, мы должны поместить пример Демо + исходный код + описание примера в файл, максимально демультиплексировать и уменьшить код, который необходимо поддерживается. Отображение примера по сути такое же, как перевод уценки, то есть уценка -> html, но нам нужно расширить правила перевода.
Есть много проблем с ручным обслуживанием при ведении документов параметров:Высокая стоимость, непростая синхронизация с кодом(Каждое изменение должно вручную изменять документ параметров), поэтому мы должны рассмотреть возможность автоматического извлечения информации из объявления ts для формирования документа параметров.
а такжеmarkdown -> html, нам на самом деле нужен толькоwebpack loaderВот и все, разбираем процесс следующим образом:
выполнить
Предварительная обработка входных файлов
Наш общий входной файл представляет собой файл уценки, который является страницей, которую мы генерируем.Вся структура страницы выглядит следующим образом (конечно, порядок этой структурырегулируемыйиз):
Выдержка из объявлений TypeScriptимя параметра,описывать,Типы,По умолчанию, мы можем использоватьreact-docgen-typescriptЭтот инструмент, мы делаем небольшую модификацию.
Настройте компилятор для фильтрации параметров, которые сами по себе не требуют отображения.
const parse = require('react-docgen-typescript').withDefaultConfig({
propFilter: (prop) => {
if (prop.parent == null) {
return true;
}
return prop.parent.fileName.indexOf('node_modules/@types/react') < 0;
},
}).parse;
Получить необходимую информацию по компоненту ts
const params = parse(filePath);
const info = params[0];
Выше мы получили информацию, которая содержит компонентимя параметра,описывать,Типы,По умолчаниюи другую информацию, нам просто нужно преобразовать эту информацию в уценкуНаписание столаЗатем вставьте его в указанное место основного файла уценки записи.
Webpack loader
Загрузчик Webpack сам обрабатывает указанный тип файла и выводит фактически упакованный код js Теперь нам нужно преобразовать уценку в фактически работающий код jsx реакции.
На предыдущем шаге мы получилиСодержит информацию о параметрахОбщий файл уценки записи, для этой уценки мы резервируем слот%%Content%%
(Конечно, мы можем указать его по желанию), который используется для вставки Демо-примера позже.
Чтобы облегчить обслуживание, мы помещаем демонстрационные примеры каждого компонента в соответствующий каталог компонентов и сохраняем их в папке с именем demo.
Каждый демонстрационный пример представляет собой файл уценки, и содержимое файла уценки выглядит следующим образом:
---
order: 0
title: 不同类型的按钮
---
按钮分为 默认按钮,主要按钮,危险按钮,高危按钮,虚线按钮,文本按钮六种。
import { Button } from '@bytedesign/web-react';
ReactDOM.render(
<Button type="primary">
Primary
</Button>,
CONTAINER
);
Файл содержит такую информацию, как заголовок, порядок отображения, описание, пример исходного кода и т. д.
Мы используем обычные или напрямуюfront-matter, вы можете получить заголовок и порядок отображения и т. д.информация о конфигурации, исходный код описательной информации и примеров можно получить с помощью регуляризации, и вся необходимая информация доступна.Далее нам нужно вставить эту информацию в нужную нам страницу.
Обработка дерева AST
Для обработки дерева AST мы решили использовать babel, который нам нужен:
AST-дерево кода представляет собой очень сложную древовидную структуру, которую мы можем передатьastexplorer.net/Этот сайт помогает создавать и просматривать деревья AST..
Для содержимого уценки мы должны использоватьmarkedПреобразуйте его в html-код. Конечно, преобразованный html — это строка. Мы не можем сгенерировать AST-дерево с помощью Babel. Нам нужно сначала преобразовать строку html в jsx. следующим образом:
function htmlToJsx(html) {
return `import React from 'react';
export default function() {
return (
<span>${html
.replace(/class=/g, 'className=')
.replace(/{/g, '{"{"{')
.replace(/}/g, '{"}"}')
.replace(/{"{"{/g, '{"{"}')}
</span>
);
};`;
}
Имея в руках код jsx, мы можем с радостью сгенерировать дерево AST:
const parser = require('@babel/parser');
function parse(codeBlock) {
return parser.parse(codeBlock, {
sourceType: 'module',
plugins: ['jsx', 'classProperties'],
});
}
const ast = parse(htmlToJsx(marked(markdown)));
Создайте AST демонстрационного примера
Получите дерево AST примера исходного кода с помощью регулярных выражений:
// @arco-design/arco-components 为抽离的用于展示示例和源码的组件
const ast = parse(`
import { CodeBlockWrapper, CellCode, CellDemo, CellDescription, Browser } from "@arco-design/arco-components";
${code}
`);
Пройдитесь по дереву AST и вставьте полученный пример AST, описание AST и AST исходного кода в компонент CodeBlockWrapper:
const traverse = require('@babel/traverse').default;
const t = require('@babel/types');
traverse(ast, {
CallExpression(_path) {
if (
_path.node.callee.object &&
_path.node.callee.object.name === 'ReactDOM' &&
_path.node.callee.property.name === 'render'
) {
const demoCellElement = t.jsxElement(
t.jsxOpeningElement(t.JSXIdentifier('CellDemo'), []),
t.jsxClosingElement(t.JSXIdentifier('CellDemo')),
[_path.node.arguments[0]]
);
const codeCellElement = t.jsxElement(
t.jsxOpeningElement(t.JSXIdentifier('CellCode'), codeAttrs),
t.jsxClosingElement(t.JSXIdentifier('CellCode')),
[codePreviewBlockAst]
);
const descriptionCellElement = t.jsxElement(
t.jsxOpeningElement(t.JSXIdentifier('CellDescription'), []),
t.jsxClosingElement(t.JSXIdentifier('CellDescription')),
[descriptionAst]
);
const codeBlockElement = t.jsxElement(
t.jsxOpeningElement(t.JSXIdentifier('CodeBlockWrapper'), []),
t.jsxClosingElement(t.JSXIdentifier('CodeBlockWrapper')),
[descriptionCellElement, demoCellElement, codeCellElement]
);
const app = t.VariableDeclaration('const', [
t.VariableDeclarator(t.Identifier('__export'), codeBlockElement),
]);
_path.insertBefore(app);
_path.remove();
}
},
});
Получите код, преобразованный, как указано выше:
const babel = require('@babel/core');
const { code } = babel.transformFromAstSync(ast, null, babelConfig);
Выходной код имеет следующий формат:
const __export = <CodeBlockWrapper>
<CellDescription>...<CellDescription>
<CellDemo>...</CellDemo>
<CellCode>...<CellCode>
</CodeBlockWrapper>
В нашей демонстрационной папке будет несколько примеров уценки, и каждый пример будет генерировать функциональный компонент и помещать его в массив:
const generate = require('@babel/generator').default;
const template = require('@babel/template').default;
const buildRequire = template(`
function NAME() {
AST
return __export;
}
`);
const finnalAst = buildRequire({
NAME: `Demo${index}`,
AST: code,
});
demoList.push(generate(finnalAst).code);
Теперь нашdemoList
По сути, это массив всех примеров компонентов компонента, и мы помещаем эти примеры в компонент, который фактически используется для отображения:
const buildRequire = template(`
CODE
class Component extends React.Component {
render() {
return React.createElement('span', { className: 'arco-components-wrapper' }, ${demoList
.map((_, index) => `React.createElement(Demo${index}, { key: ${index} })`)
.join(',')});
}
}
`);
const finnalAst = buildRequire({
CODE: demoList.join('\n'),
});
OK, FINNALAST — это AST в AST, сгенерированный вставляемой записью Markdown.
заменить заполнитель
Помните, мы оставили%%Content%%
заполнитель, теперь нам нужно заменить обработанный пример на место заполнителя.
traverse(contentAst, {
JSXElement: (_path) => {
if (
_path.node.openingElement.name.name === 'p' &&
_path.node.children[0].value === '%%Content%%'
) {
const expresstion = t.jsxExpressionContainer(
t.jsxElement(
t.jsxOpeningElement(t.JSXIdentifier('Component'), [], true),
null,
[],
true
)
);
_path.replaceWith(expresstion);
_path.stop();
}
},
});
// 把我们处理的示例ast放到函数声明前
traverse(contentAst, {
FunctionDeclaration: (_path) => {
_path.insertBefore(finnalAst);
_path.stop();
},
});
Загрузчик Webpack, наконец, обрабатывает возвращенный код:
return generate(contentAst).code;
использовать
В соответствии с описанным выше процессом мы успешно создали загрузчик уценки, который обрабатывает файлы уценки и будет обрабатывать демо-примеры.После использования этого плагина мы можем использовать его следующим образом:
import ButtonPage from 'components/Button/README.md';
function Page() {
return <ButtonPage />;
}
Суммировать
Выше мы использовали загрузчик веб-пакетов для создания документов компонентов на основе уценки, Этот процесс фактически решил многие из наших проблем при написании документов компонентов:
- Убедитесь, что документы параметров полностью синхронизированы с исходным кодом, что значительно снижает затраты на обслуживание.
- Отображение примеров на официальном веб-сайте, отладка компонентов и т. д. выполняются за один шаг, а затраты на написание примеров сведены к минимуму. Просто напишите код один раз, и его можно использовать для генерации одновременноПример официального сайта,Исходный код примера официального сайта,Тесты моментальных снимков, страницы справки Github/Gitlab. Более того, этот процесс полностью автоматизирован, и он может в основном сосредоточиться только на написании логики компонентов, что значительно снижает затраты на разработку и обслуживание.
- Воспользовавшись тем, что файлы уценки изначально анализируются и отображаются gitlab и github, у нас есть страница описания в каждом каталоге компонентов, где вы можете увидеть такую информацию, как параметры и описания.
- Полностью управляемый стиль официального сайта.
Если вы также разрабатываете библиотеку компонентов React или вам нужно показать примеры компонентов, связанных с React, этот документ может вам помочь.