Plus 组件

Markdown 渲染器

在网页中即时渲染 Markdown 和 MDX

使用场景

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包产物,一键完成代码接入


![Abstract](/assets/root-web-sites/abstract.jpg)

---

#### 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

属性说明类型默认值
rawMarkdown 或 MDX 的纯文本。变化时重新编译。stringundefined
components用于覆盖 Markdown 元素,也可添加 MDX 中引用的自定义组件;值为 Vue 组件或 HTML 标签名,元素键(如 h1)只接受组件或自身的标签名。与 MarkdownRender.defaultComponents 合并,变化时只重新渲染,不重新编译。MarkdownRenderComponentssemiGlobalDefault("MarkdownRender", "components")
formatraw 的格式,md 为纯 Markdown,mdx 允许内嵌 JSX。变化时重新编译。"md" | "mdx"semiDefault("MarkdownRender", "format", "mdx")
remarkPlugins自定义 Remark 插件,数组元素变化时重新编译。PluggableListsemiGlobalDefault("MarkdownRender", "remarkPlugins")
rehypePlugins自定义 Rehype 插件,数组元素变化时重新编译。PluggableListsemiGlobalDefault("MarkdownRender", "rehypePlugins")
remarkGfm是否开启 GitHub GFM 语法。变化时重新编译。booleansemiDefault("MarkdownRender", "remarkGfm", true)

Events

事件说明回调参数
无

Slots

插槽说明作用域参数
无

Exposes

名称说明类型
无

设计变量

下表是组件内部的 Sass 变量,不能覆写。定制样式请覆写 --semi-* CSS 变量(见 Design Token 页),或写不分层的 CSS 规则。
Sass 变量值用法
$spacing-markdownRender_component_image-marginLeft16px
$spacing-markdownRender_component_image-marginRight16px
$spacing-markdownRender_component_image-marginTop16px
$spacing-markdownRender_component_image-marginBottom16px
$width-markdownRender_component_image-maxWidth50%
$width-markdownRender_component_image-maxHeight500px
$spacing-markdownRender_component_image_alt-marginTop8px
$spacing-markdownRender_component_header1-marginTop16px
$spacing-markdownRender_component_header1-marginBottom16px
$spacing-markdownRender_component_header2-marginTop16px
$spacing-markdownRender_component_header2-marginBottom16px
$spacing-markdownRender_component_header3-marginTop16px
$spacing-markdownRender_component_header3-marginBottom16px
$spacing-markdownRender_component_header4-marginTop16px
$spacing-markdownRender_component_header4-marginBottom16px
$spacing-markdownRender_component_header5-marginTop16px
$spacing-markdownRender_component_header5-marginBottom16px
$spacing-markdownRender_component_header6-marginTop16px
$spacing-markdownRender_component_header6-marginBottom16px
$spacing-markdownRender_component_list-paddingLeft24px
$spacing-markdownRender_component_list-marginTop16px
$spacing-markdownRender_component_list-marginBottom16px
$color-markdownRender_simpleCode-bgvar(--semi-color-fill-0)
$color-markdownRender_simpleCode-textvar(--semi-color-text-1)
$color-markdownRender_component_listvar(--semi-color-text-0)
$color-markdownRender_component_image_altvar(--semi-color-tertiary)