使用场景
Markdown 是一种文档标记语言,可以通过简单的标记实现例如标题,图片,表格,链接,加粗等基本常用富文本功能。MDX 是在 Markdown 基础上,允许引入 JSX 实现更加复杂定制化的文档撰写与展示需求。
Semi 提供的 MarkdownRender 组件支持渲染 Markdown 和 MDX,无需特别配置,传入纯文本即可渲染出符合 Semi 样式规范的富文本内容。代码块由 CodeHighlight 高亮,用到的语言需先通过 CodeHighlight.register 注册,未注册的语言按纯文本显示。
通常用于下列场景:
- 文档站编写与渲染
- 服务端动态生成富文本内容时,前端渲染
- 偏内容展示的轻交互网站
代码演示
如何引入
<script setup lang="ts">
import { MarkdownRender } from '@fibrous/semi-ui-vue';
</script>
基本用法
导入 MarkdownRender 后,直接传入 Markdown 或 MDX 纯文本即可。
注意因为 < { 等符号是合法的 JSX 符号会被判定为代码,无法直接渲染,需要使用 \ 转义,如果你只需要渲染纯 Markdown,参考下方仅渲染 Markdown 一节。
代码
<script setup lang="ts">
import { MarkdownRender } from '@fibrous/semi-ui-vue';
const raw = `
正文内容是普通的文本,也可以**加粗**~~删除线~~和<u>下划线</u> [超链接](https://semi.design) 等 Markdown 与 HTML 的基本语法所支持的富文本,也支持 emoji 🍰
部分符号需要转义 \\{\\} \\<\\> ...
<br/>
<br/>
---
#### Semi Design DSM
[Semi DSM](https://semi.design/dsm) 是 Semi Design 提供的设计系统管理工具(Design System Management),支持全局、组件级别的样式定制,并在 Figma 和前端代码之间保持同步\\
适用于各种规模的团队,无论你是需要简化工作流程,提高团队协作,还是增加生产力,我们都有适合你的功能
##### 中大型企业
- 多达 3000+ Design Token,深入每一处细节的定制可能,色彩,阴影,边距,圆角,动效,渲染结构均可自由定制,告别 ~~CSS 硬编码~~
- 功能强大,经过抖音内部数千项目验证过的 UI lib,轻松应对各类复杂场景
- A11y 无障碍友好,国际化功能完备
- 面向社区建设,完全开源,无使用限制
- 从 designOps 到 devOps,自动化工作流,Figma UI Kit 一键刷入主题,生成 Style Guideline,研发一行 npm 代码配置接入
##### 初创企业
- 无需从 0 到 1 投入大量研发资源,快速复用开源社区优秀方案, 低成本快速定制具备品牌特色的设计系统。
- 一键支持暗色模式生成,支持根据品牌色快速生成包含 320 个全色阶、兼容深/浅两种模式的色彩系统,并支持动态切换
- 不断进化,DSM + Semi Design 组件由<u>抖音前端架构团队</u>专业维护,已稳定迭代五年+,值得信赖
##### 自由设计师/个人开发者
- 低成本快速创建风格各异的设计系统,更少时间,更快交付
- 研发接入友好,无需反复沟通,交付npm包产物,一键完成代码接入

---
#### MarkdownRender 渲染列表语法
- 好好地吃饭
- 好好地睡觉
- 好好地游玩
- 好好地学习
- 好好地聊天
- 好好地吵架
- 过着平凡普通的每日
| 支持 | Markdown 表格 | c | d |
| - | :- | -: | :-: |
| 1 | 2 | 3 | 4 |
| 21 | 22 | 23 | 24 |
| 31 | 32 | 33 | 34 |
| 41 | 42 | 43 | 44 |
`;
</script>
<template>
<MarkdownRender :raw="raw" />
</template>修改元素样式
你可以任意替换 Markdown 或 MDX 文档中的文档元素的显示效果,只需向 components 属性传入你的 Vue 组件覆盖即可。元素的属性(如 class、colspan)以 HTML 属性名交给组件,由 attrs 读取;元素的内容通过默认插槽传入。
比如,现在需要将所有2号标题的颜色设置成主色
代码
<script setup lang="ts">
import { h, type FunctionalComponent } from 'vue';
import { MarkdownRender, TypographyTitle, type MarkdownRenderComponents } from '@fibrous/semi-ui-vue';
// `components` takes component objects, which a template cannot define inline.
const PrimaryTitle: FunctionalComponent = (_, { slots }) => h(TypographyTitle, { heading: 2, style: { color: 'var(--semi-color-primary)' } }, () => slots.default?.());
const components: MarkdownRenderComponents = { h2: PrimaryTitle };
</script>
<template>
<MarkdownRender :components="components" raw="## 从 Semi Design 到 Any Design 快速定义你的设计系统,并应用在设计稿和代码中" />
</template>任何由 Markdown 生成的元素标签都可以覆盖,常用的有 a blockquote br code del em h1 h2 h3 h4 h5 h6 hr img li ol p pre strong ul table
仅纯 Markdown
当你渲染的 Markdown 仅仅是纯 markdown,不包含任何 JSX 代码时,可传入 format="md" 来开启仅 Markdown 模式,在这种模式下无需转义特殊字符
无需转义的符号{}<> ...
代码
<script setup lang="ts">
import { MarkdownRender } from '@fibrous/semi-ui-vue';
</script>
<template>
<MarkdownRender format="md" raw="无需转义的符号{}<> ..." />
</template>format="md" 经 unified 管线渲染:remark-parse 解析,依次执行 remark-gfm(remarkGfm 为 true 时)与 remarkPlugins,remark-rehype 转为 HTML 语法树,执行 rehypePlugins,最后由 hast-util-to-jsx-runtime 生成 Vue 虚拟节点并套用 components。整个过程不生成、不执行代码,在禁用 unsafe-eval 的 CSP 下也能渲染,也不会加载 MDX 编译器。
format="mdx" 经 @mdx-js/mdx 把源文本编译成代码并在运行时执行,CSP 需要允许 unsafe-eval;@mdx-js/mdx 在首次渲染 MDX 时按需加载。
<script setup lang="ts">
import { MarkdownRender } from '@fibrous/semi-ui-vue';
import rehypeRaw from 'rehype-raw';
const rehypePlugins = [rehypeRaw];
</script>
<template>
<MarkdownRender format="md" raw='<span style="color:red">红色文字</span>' :rehypePlugins="rehypePlugins" />
</template>
添加自定义组件
通过传入自定义组件到 components 属性,能够实现在 Markdown 中直接书写 JSX,组件会被渲染到最终页面上,支持 JS 事件。默认的 Markdown 组件可从 MarkdownRender.defaultComponents 中获取,可以用于二次封装。
MDX 中的 JSX 属性按 Vue 的规则交给组件:className 并入 class,style 可写字符串;监听器以 onClick、onChange、onInput、onFocus、onBlur 传入,组件用 emit 或 attrs 接收,其余事件按 Vue 命名,如 onMouseenter、onKeydown、onDblclick。
代码
<script setup lang="ts">
import { h, type FunctionalComponent } from 'vue';
import { Button, MarkdownRender, type MarkdownRenderComponents } from '@fibrous/semi-ui-vue';
// `components` takes component objects, which a template cannot define inline. The MDX source refers to it by name;
// the onClick it passes falls through to the Button.
const MyButton: FunctionalComponent = (_, { slots }) => h(Button, { type: 'primary', style: { marginBottom: '12px' } }, () => slots.default?.());
const components: MarkdownRenderComponents = { ...MarkdownRender.defaultComponents, MyButton };
const raw = `
#### 下面是一个渲染在 Markdown 中的按钮
<MyButton onClick={()=>alert("点击了 MyButton")}>MyButton 点我</MyButton>
直接在 Markdown 中书写 JSX 即可
`;
</script>
<template>
<MarkdownRender :components="components" :raw="raw" />
</template>替换内置的 code 组件时,围栏代码块的源码作为文本节点出现在默认插槽中,把默认插槽返回的节点交给 codeText 即可取得源码(保留末尾换行,未做 trim);内置组件也可以从深路径单独导入:
import { codeText } from '@fibrous/semi-ui-vue/markdown-render/index';
import { code, pre } from '@fibrous/semi-ui-vue/markdown-render/components/index';
添加插件
通过 remarkPlugins rehypePlugins 支持 unified 生态的 Remark 与 Rehype 插件,详情请参考 MDXJS 与 unified
raw、format、remarkGfm 变化,或插件数组中的元素变化时会重新编译;components 变化只重新渲染,不重新编译。插件数组按元素比较,模板里的数组字面量只要元素相同就不会重新编译;[插件, 配置对象] 写法中的配置对象也按引用比较,写成模板字面量会在每次渲染时重新编译,需要在 script setup 中定义以保持引用稳定:
<script setup lang="ts">
import { MarkdownRender } from '@fibrous/semi-ui-vue';
import type { MarkdownRenderProps } from '@fibrous/semi-ui-vue';
import remarkToc from 'remark-toc';
defineProps<{ raw: string }>();
const remarkPlugins: MarkdownRenderProps['remarkPlugins'] = [[remarkToc, { heading: '目录' }]];
</script>
<template>
<MarkdownRender format="md" :raw="raw" :remarkPlugins="remarkPlugins" />
</template>
编译出错
MDX 语法错误或插件抛错时,保留上一次成功渲染的内容,开发环境下通过 console.error 输出错误,不会产生未处理的 Promise rejection。
无障碍
传入 aria-label 或 aria-labelledby 且没有 role 时,根元素带 role="group",使用方显式传入的 role 优先。链接内的图片不可预览,链接是唯一的交互元素;图片下方的说明文字对辅助技术隐藏,图片名称只来自 alt。
API
MarkdownRender 没有默认插槽和事件,class、style、id、data-* 与监听器落在根元素 div 上。
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| raw | Markdown 或 MDX 的纯文本。变化时重新编译。 | string | undefined |
| components | 用于覆盖 Markdown 元素,也可添加 MDX 中引用的自定义组件;值为 Vue 组件或 HTML 标签名,元素键(如 h1)只接受组件或自身的标签名。与 MarkdownRender.defaultComponents 合并,变化时只重新渲染,不重新编译。 | MarkdownRenderComponents | semiGlobalDefault("MarkdownRender", "components") |
| format | raw 的格式,md 为纯 Markdown,mdx 允许内嵌 JSX。变化时重新编译。 | "md" | "mdx" | semiDefault("MarkdownRender", "format", "mdx") |
| remarkPlugins | 自定义 Remark 插件,数组元素变化时重新编译。 | PluggableList | semiGlobalDefault("MarkdownRender", "remarkPlugins") |
| rehypePlugins | 自定义 Rehype 插件,数组元素变化时重新编译。 | PluggableList | semiGlobalDefault("MarkdownRender", "rehypePlugins") |
| remarkGfm | 是否开启 GitHub GFM 语法。变化时重新编译。 | boolean | semiDefault("MarkdownRender", "remarkGfm", true) |
Events
| 事件 | 说明 | 回调参数 |
|---|---|---|
| 无 |
Slots
| 插槽 | 说明 | 作用域参数 |
|---|---|---|
| 无 |
Exposes
| 名称 | 说明 | 类型 |
|---|---|---|
| 无 |
设计变量
| Sass 变量 | 值 | 用法 |
|---|---|---|
| $spacing-markdownRender_component_image-marginLeft | 16px | |
| $spacing-markdownRender_component_image-marginRight | 16px | |
| $spacing-markdownRender_component_image-marginTop | 16px | |
| $spacing-markdownRender_component_image-marginBottom | 16px | |
| $width-markdownRender_component_image-maxWidth | 50% | |
| $width-markdownRender_component_image-maxHeight | 500px | |
| $spacing-markdownRender_component_image_alt-marginTop | 8px | |
| $spacing-markdownRender_component_header1-marginTop | 16px | |
| $spacing-markdownRender_component_header1-marginBottom | 16px | |
| $spacing-markdownRender_component_header2-marginTop | 16px | |
| $spacing-markdownRender_component_header2-marginBottom | 16px | |
| $spacing-markdownRender_component_header3-marginTop | 16px | |
| $spacing-markdownRender_component_header3-marginBottom | 16px | |
| $spacing-markdownRender_component_header4-marginTop | 16px | |
| $spacing-markdownRender_component_header4-marginBottom | 16px | |
| $spacing-markdownRender_component_header5-marginTop | 16px | |
| $spacing-markdownRender_component_header5-marginBottom | 16px | |
| $spacing-markdownRender_component_header6-marginTop | 16px | |
| $spacing-markdownRender_component_header6-marginBottom | 16px | |
| $spacing-markdownRender_component_list-paddingLeft | 24px | |
| $spacing-markdownRender_component_list-marginTop | 16px | |
| $spacing-markdownRender_component_list-marginBottom | 16px | |
| $color-markdownRender_simpleCode-bg | var(--semi-color-fill-0) | |
| $color-markdownRender_simpleCode-text | var(--semi-color-text-1) | |
| $color-markdownRender_component_list | var(--semi-color-text-0) | |
| $color-markdownRender_component_image_alt | var(--semi-color-tertiary) |