Bladeren bron

使用ast将markdown转为vnode提高流式渲染性能

kagg886 3 maanden geleden
bovenliggende
commit
c1784844ff
4 gewijzigde bestanden met toevoegingen van 87 en 20 verwijderingen
  1. 1 0
      package.json
  2. 39 18
      src/components/markdown/index.vue
  3. 1 1
      src/views/assistant/index.vue
  4. 46 1
      yarn.lock

+ 1 - 0
package.json

@@ -40,6 +40,7 @@
     "element-plus": "2.2.28",
     "event-source-polyfill": "1.0.31",
     "html2canvas": "^1.4.1",
+    "htmlparser2": "^10.0.0",
     "js-cookie": "3.0.5",
     "jsplumb": "2.15.6",
     "jsrsasign": "10.8.6",

+ 39 - 18
src/components/markdown/index.vue

@@ -1,15 +1,15 @@
 <template>
-	<div
-		ref="container"
-		class="markdown-content"
-		v-html="renderedContent"
-	/>
+	<div>
+		<MarkdownNodeRenderer v-for="(node, index) in renderedContent" :key="index" :node="node" />
+	</div>
 </template>
 
 <script setup lang="ts">
-import { ref, computed} from 'vue'
+import { computed, defineComponent, h } from 'vue'
 import MarkdownIt from 'markdown-it';
 import { MarkdownPlugin } from '/@/components/markdown/types'
+import { parseDocument } from 'htmlparser2'
+import DOMPurify from 'dompurify'
 
 interface Props {
 	content: string
@@ -20,8 +20,6 @@ const props = withDefaults(defineProps<Props>(), {
 	plugins: undefined
 })
 
-const container = ref<HTMLElement>()
-
 const md = new MarkdownIt({
 	html: true,
 	linkify: true,
@@ -32,17 +30,40 @@ for (const plugin of props.plugins ?? []) {
 	md.use(plugin.impl, plugin.settings())
 }
 
-// 渲染markdown内容
 const renderedContent = computed(() => {
-	try {
-		return md.render(props.content)
-	} catch (error) {
-		console.error('Markdown渲染错误:', error)
-		return `<div style="color: red; padding: 20px; border: 1px solid red; border-radius: 4px;">
-      <strong>Markdown渲染错误:</strong><br>
-      ${error instanceof Error ? error.message : String(error)}
-    </div>`
-	}
+	// Markdown模式添加安全过滤和样式类,并处理成dom ast
+	return parseDocument(
+		md.render(props.content),
+	).children
+})
+
+
+
+const MarkdownNodeRenderer = defineComponent({
+	name: 'MarkdownNodeRenderer',
+	props: {
+		node: {
+			type: Object,
+			required: true,
+		},
+	},
+	setup(props) {
+		return () => {
+			const { node } = props;
+
+			if (node.type === 'text') {
+				return node.data
+			} else {
+				return h(
+					node.tagName,
+					{ ...node.attribs },
+					node.children.map((child, index) =>
+						h(MarkdownNodeRenderer, { node: child, key: index })
+					)
+				)
+			}
+		}
+	},
 })
 </script>
 

+ 1 - 1
src/views/assistant/index.vue

@@ -2,7 +2,7 @@
 import { defineMarkdownPlugin, MarkdownPlugin } from '/@/components/markdown/types'
 import echartsPlugin, { EchartsPluginOptions } from '/@/components/markdown/plugins/markdown-it-echarts'
 import Markdown from '/@/components/markdown/index.vue'
-import { ref, nextTick, onMounted, computed } from 'vue'
+import { ref, nextTick, onMounted } from 'vue'
 import { Local } from '/@/utils/storage'
 import { User, ChatDotRound, Delete, Edit } from '@element-plus/icons-vue'
 

+ 46 - 1
yarn.lock

@@ -1597,6 +1597,36 @@ doctrine@^3.0.0:
   dependencies:
     esutils "^2.0.2"
 
+dom-serializer@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53"
+  integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==
+  dependencies:
+    domelementtype "^2.3.0"
+    domhandler "^5.0.2"
+    entities "^4.2.0"
+
+domelementtype@^2.3.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
+  integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
+
+domhandler@^5.0.2, domhandler@^5.0.3:
+  version "5.0.3"
+  resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31"
+  integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==
+  dependencies:
+    domelementtype "^2.3.0"
+
+domutils@^3.2.1:
+  version "3.2.2"
+  resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.2.2.tgz#edbfe2b668b0c1d97c24baf0f1062b132221bc78"
+  integrity sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==
+  dependencies:
+    dom-serializer "^2.0.0"
+    domelementtype "^2.3.0"
+    domhandler "^5.0.3"
+
 dotenv@^16.4.5:
   version "16.5.0"
   resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz"
@@ -1677,11 +1707,16 @@ element-resize-detector@^1.2.1:
   dependencies:
     batch-processor "1.0.0"
 
-entities@^4.4.0, entities@^4.5.0:
+entities@^4.2.0, entities@^4.4.0, entities@^4.5.0:
   version "4.5.0"
   resolved "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz"
   integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
 
+entities@^6.0.0:
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/entities/-/entities-6.0.1.tgz#c28c34a43379ca7f61d074130b2f5f7020a30694"
+  integrity sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==
+
 es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0:
   version "1.23.3"
   resolved "https://registry.npmmirror.com/es-abstract/-/es-abstract-1.23.3.tgz"
@@ -2362,6 +2397,16 @@ html2canvas@^1.4.1:
     css-line-break "^2.1.0"
     text-segmentation "^1.0.3"
 
+htmlparser2@^10.0.0:
+  version "10.0.0"
+  resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-10.0.0.tgz#77ad249037b66bf8cc99c6e286ef73b83aeb621d"
+  integrity sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==
+  dependencies:
+    domelementtype "^2.3.0"
+    domhandler "^5.0.3"
+    domutils "^3.2.1"
+    entities "^6.0.0"
+
 ignore@^5.1.8, ignore@^5.2.0:
   version "5.3.2"
   resolved "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz"