说明

经过上阶段的配置虽然可以在项目中使用 mdx 语法 来创建页面了,但是我们的组件库有一些定制化的需求:交互式的组件演示、组件 Props 列表展示。这些功能如果可以通过封装来实现,会大大提升开发效率

Props 与 Playground 的对外接口,将会仿照 docz 的 Props Playground

相关文章

  • 基于 next.js + mdx 搭建组件库文档项目(一) – 开发环境搭建
  • 基于 next.js + mdx 搭建组件库文档项目(二) – mdx 控件封装实现组件的演示与 Props 列表

1. Props 控件实现

Props 是用来展示组件属性列表的,参考 docz 提供的 api 实现, 我们需要将组件传给 这个控件,让这个控件获得 属性列表后展示

我们的组件是通过 Typescript 开发的,在组件 build 后 TS 相关的类型声明会被剥离,因此,传入 Props 控件中的组件需要是未编译的 ts 文件,这一点可以通过上述 webpack alias 来实现

config.resolve.alias = {...config.resolve.alias,"@mjz-test/mjz-ui": path.resolve(__dirname, "../../../mjz-ui/src"), // 配置去获取源文件, 这样可以通过解析拿到组件的 TS 类型"@mjz-test/icons": path.resolve(__dirname, "../../../icons/src"),
}

虽然使用了 ts 的源文件,但是组件本身的携带的数据中并没有 props 列表,好在社区中有一个插件可以帮助我们将组件的 props 解析出来,并且挂载到组件对象上

/** ----- next.config.js -----  */
module.exports = withMDX({webpack(config, nextConfig) {// add less compileconfig = injectLessLoader(config, nextConfig);// add aliasconfig = injectAlias(config);// include docgen ruleconfig = addJsxInclude(config, nextConfig.defaultLoaders);return config},
});/** ----- addJsxInclude.js -----  */
const path = require('path');
const cloneDeep = require('lodash/cloneDeep');const addJsxInclude = (config, defaultLoaders) => {// 1. 找到 webpack 配置中处理 ts 的那个 ruleconst jsxRuleIdx = config.module.rules.findIndex((rule) => rule.test instanceof RegExp && rule.test.test('xxx.tsx'));const jsxRule = config.module.rules[jsxRuleIdx];const rule = cloneDeep(jsxRule);// 创建一个新的 rule 专门处理 需要显示 props 的组件rule.test = /\.(tsx|ts)$/;rule.include = [path.resolve(__dirname, '../../../next-docs/docs'),path.resolve(__dirname, '../../../mjz-ui'),path.resolve(__dirname, '../../../icons')];rule.use = [defaultLoaders.babel, // next 自带的 babel 处理程序{loader: 'react-docgen-typescript-loader',options: {tsconfigPath: path.resolve(__dirname, "../../tsconfig.json"),// 这个配置可以使得 enum 类型转为 union 类型输出shouldExtractLiteralValuesFromEnum: true,// 排除对原生标签属性的打包,HTML 原生标签属性都是从 @types/react 继承出来的, 通过以下操作排除打包propFilter: (prop) => {if (prop.declarations !== undefined && prop.declarations.length > 0) {const hasPropAdditionalDescription = prop.declarations.find((declaration) => {return !declaration.fileName.includes("node_modules");});return Boolean(hasPropAdditionalDescription);}return true;}}}]config.module.rules.splice(jsxRuleIdx, 0, rule);return config;
}module.exports = addJsxInclude;

经过上边的 react-docgen-typescript-loader 处理后,每个导入的组件就都包含 __docgenInfo 属性了,我们预计通过 <Props of={Button}/> 来获得组件的props 展示,下边是 Props 控件的实现

import React, { ComponentType } from 'react';
import get from 'lodash/get';
import isObject from 'lodash/isObject';export type ComponentWithDocGenInfo = ComponentType & {__docgenInfo: {description?: string;props?: Record<string, DocProp>;};
};interface PropsProps {of: ComponentWithDocGenInfo;
}const hasSymbol = typeof Symbol === 'function' && Symbol.for;
const ReactMemoSymbol = hasSymbol? Symbol.for('react.memo'): typeof React.memo === 'function' && React.memo(() => null).$$typeof;class Props extends React.PureComponent<PropsProps> {getDocProps() {const { of } = this.props;const commonProps = get(of, '__docgenInfo.props');const memoProps = get(of, 'type.__docgenInfo.props');// React.memo 包裹的组件有些特殊if (ReactMemoSymbol && get(of, '$$typeof') === ReactMemoSymbol) {if (!isObject(memoProps) && !isObject(commonProps)) return [];return isObject(memoProps) ? Object.values(memoProps) : Object.values(commonProps);}return isObject(commonProps) ? Object.values(commonProps) : [];}getDocDefaultProps() {const { of } = this.props;const commonDef = get(of, 'defaultProps');const memoDef = get(of, 'type.defaultProps');if (ReactMemoSymbol && get(of, '$$typeof') === ReactMemoSymbol) {if (!isObject(memoDef) && !isObject(commonDef)) return {};return isObject(memoDef) ? memoDef : commonDef;}return isObject(commonDef) ? commonDef : {};}render(): React.ReactNode {const docProps = this.getDocProps();const docDefaultProps = this.getDocDefaultProps();return (<div>{docProps.map((docProp) => {return (<div><p>{docProp.name}</p><p>{docProp.description}</p><p>{getTypeStr(docProp.type)}</p><p>{getDefaultValue(docProp.defaultValue)}</p><p>{docProp.required}</p></div>);})}</div>);}
}export default Props;

2. Playground 控件实现

Playground 控件用来提供一个组件的交互式演示区域,这个区域包括:组件展示、源码展示、源码编辑后实时更新;

这个控件我们会借助社区中的 react-live + 一些定制来实现,react-live 提供的 LiveProvider 中需要传入两个重要属性 code (源码) scope(源码中的一些上下文)

组件 code 与 scope 的收集比较棘手,我们需要借助 mdx 的插件系统,来开发一个自定义插件,我参考了 docz 的实现(copy),整体的思路是:

  • 在 mdx 被编译为 js 的过程中,mdx 文件中 的 Playground 组件会被单独处理
  • 传入 Playground 的 children 会被转为字符串后作为 __code 属性传入
  • 会在接卸 mdx 的 AST 树中拿到 import 的资源作为 __scope 属性传入

具体插件实现代码

插件如何使用

const rehypePlugin = require('./scripts/rehyoe-plugin/rehype');
const withMDX = require("@next/mdx")({extension: /\.mdx?$/,options: {remarkPlugins: [],rehypePlugins: [rehypePlugin]}
});
module.exports = withMDX({/.../});

我们期待 Playground 组件只需要传入 children 即可,其实现如下

import React from 'react';
import { Language, PrismTheme } from 'prism-react-renderer';
import { LiveProvider, LiveError, LiveEditor, LivePreview } from 'react-live';
import { mdx } from '@mdx-js/react';
import { theme } from '../Code';
import PlaygroundLess from './playground.module.less';interface Props {hideCode?: boolean;__code: string;__scope: Record<string, any>;__position: number;language?: Language;
}const transformCode = (code: string) => {if (code.startsWith('()') || code.startsWith('class')) return code;return `<React.Fragment>${code}</React.Fragment>`;
};class Playground extends React.PureComponent<Props> {static defaultProps = {language: 'jsx',__code: '',__scope: {},__position: 0,};render(): React.ReactNode {const { __scope, __code, language } = this.props;return (<div className={PlaygroundLess.container}><LiveProviderclassName={PlaygroundLess.provider}code={__code}transformCode={transformCode}language={language}theme={theme as PrismTheme}scope={{ mdx, ...__scope }}><LivePreview /><LiveEditor /><LiveError /></LiveProvider></div>);}
}export default Playground;

如果需要对 编辑及展示区域做更多的定制化,可以自由组合 <LivePreview /> <LiveEditor /> ,或者可以仿照 其源码,做自己的实现

3. Anchor 页面锚点收集控件实现

一个组件的文档页可能会比较长,一般来讲都会实现一个吸顶的 menu 区域来做锚点跳转

为了自动化的实现这个功能,我们需要如下操作

引入 remark-slug ,他可以作为 mdx 的插件来使用,其作用是将 1~5 级标题,转化为带有 id 的组件

const slug = require('remark-slug');
const withMDX = require("@next/mdx")({extension: /\.mdx?$/,options: {remarkPlugins: [slug],rehypePlugins: [rehypePlugin]}
});
module.exports = withMDX({});

转化后的标题怎样在项目中收集呢?前边聊过 mdx 的 MDXProvider 可以通过传入的 components 来映射 markdown 语法对应的组件,这里还包括一个特殊的映射 wrapper

wrapper 是包裹在左右 mdx 解析后组件外层的组件,它接收 children 属性,我们可以通过遍历 children 找出 1~5 级标题的标签(例 child.props.mdxType === ‘h2’)然后将其渲染成一个组件即可。

具体示例查看

基于 next.js + mdx 搭建组件库文档项目(二) -- mdx 控件封装实现组件的演示与 Props 列表相关推荐

  1. 基于 next.js + mdx 搭建组件库文档项目(一) -- 开发环境搭建

    说明 之前使用过 Docz 来作为组件库文档搭建工具,它基于 gatsby , 提供了高度的定制化能力,但是截止 2021-06-22, Docz 停留在 v2.3.1(2020-04-05) 已经一 ...

  2. StoryBook 开发React组件库文档

    StoryBook 开发 React 组件库文档 说明 StoryBook 是一个开源的 UI 组件库构建工具,支持 React.Vue.Angular 等主流开发框架,使用 StoryBook 将获 ...

  3. 使用VitePress静态网站生成器创建组件库文档网站并部署到GitHub

    Vue3+TS+Vite开发组件库并发布到npm 网站在线预览: Vue Amazing UI | Amazing UI Components LibraryAmazing UI 组件库https:/ ...

  4. 组件库系列三:编写组件库文档

    文章目录 vuepress介绍 创建文档工程 配置运行指令 vuepress浏览器自动更新 下载插件和依赖 npm/yarn link docs文件夹 .vuepress文件夹 可收缩代码块 效果展示 ...

  5. DevExpress WinForms帮助文档:表单控件 - Taskbar Assistant

    点击获取DevExpress完整版下载 Windows 7为系统任务栏中显示的应用程序按钮引入新的外观和新功能,Taskbar Assistant组件旨在帮助自定义应用程序的任务栏按钮.其跳转列表和缩 ...

  6. 基于云开发的微信答题活动小程序v1.0搭建部署帮助文档

    11月是全国"119"消防宣传月,不少企事业单位都会举办消防安全知识竞答活动,因此我基于云开发搭建了消防安全知识答题活动小程序. 接着,还写完了初阶的手把手教你搭建答题活动小程序系 ...

  7. asp.net 安装element ui_Vue组件库系列三:打造属于自己的 UI 库文档(新版本的方案)...

    上一章介绍了element-ui老版本中md文档构建过程,如果大家认真看了,就会发现在.md文档中还是有一些重复的部分,怎样使文档看着更加简洁,操作更加方便,饿了么团队下了很大的力气做了探索,现在我们 ...

  8. 【Auto.JS】Autojs官方提取文档使用说明函数 (2)

    接上一篇文章:Autojs官方提取文档使用说明函数 (1) Images Stability: 2 - Stable images模块提供了一些手机设备中常见的图片处理函数,包括截图.读写图片.图片剪 ...

  9. go第三方库文档 日志构建zap

    // go第三方库文档 日志构建zap // https://pkg.go.dev/go.uber.org/zap// 安装 // go get-u go.uber.org/zapQuick Star ...

最新文章

  1. 面试必备的 23 个JVM 面试真题!
  2. 弄清指针-如何深入了解指针
  3. drcom linux怎么运行,drcom for linux
  4. 485通讯转换器产品功能特点介绍
  5. 各大公司应聘电子类题目精选
  6. excel怎么设置自动计算_Excel怎么计算所占百分比?
  7. 安装金山词霸2007
  8. RSA算法生成2048位公私钥
  9. matlab牛顿插值法
  10. javascript学习笔记最全
  11. 百度贴吧安卓客户端网络通信行为分析
  12. 版本控制系统(vcs)Git
  13. 网页设计专家票选的16款常用英文字体
  14. springboot项目中favicon.ico的异常处理
  15. 建模师是个好职业吗?
  16. Java中string字符串和char字符之间的千丝万缕
  17. flink sql 执行源码走读全流程
  18. 如何摆脱极域2016(及以下版本)的控制
  19. 实现一个小轮子:用AOP实现异步上传
  20. 拔掉U盘时提示无法停止使用通用卷问题如何解决

热门文章

  1. ACL扩展IP访问控制列表配置
  2. 重磅!2019中山入户最新资讯,教你如何解决入户难题
  3. IBM serverx440 光通路诊断面板
  4. MDC300平台介绍
  5. Leetcode Weekly 188 解题报告
  6. 《计算机应用基础》在线作业一
  7. SPSS数据分析前,异常值处理
  8. Excel选中区域全都除以某一个数
  9. uniapp使用l-painter画板,海报
  10. Fiddler抓取微信公众号数据