-
-
Notifications
You must be signed in to change notification settings - Fork 876
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
支持渲染 React, Vue 组件 #1188
Comments
这个应该可以嵌入第三方的代码块功能就好了吧 |
按照vditor的渲染,光写自定义渲染器可能做不到😂就是我之前在链滴提的要抽离出来的部分特性。 |
第三方的不是给一个 iframe 就可以使用了? |
不,是支持原生组件开发渲染😂而不是渲染代码展示效果 |
是不是类似于 vditor-react 这种新建一个包装的项目? |
不是,可以参考dumi的写组件demo |
还是不太懂 🤦♀️ |
emmmm 是可以直接把组件渲染成真实 HTML 的一项功能,在 slidev、vitepress、vuepress 等里面都有 <!-- Markdown 内容 -->
这是段落文本
```react:component
const Component = () => <div>Hello React Component</div>
export default Component
``` 渲染的结果会是这样: <!-- Markdown 内容 -->
<p>这是段落文本</p>
<pre>
<div>Hello React Component</div>
</pre> |
感觉和我第一次的回复的东西是一样的。 https://b3log.org/vditor/demo/react.html 这样使用会更加安全。如果需要展示结果的话可以直接使用 html |
😂不一样,说白了组件就是封装了HTML,对于文档工具的方向还是很有意义的。而codepen是一个沙盒,不是HTML注入 |
是不太一样。这个是否可以考虑使用你前面说的插件,因为内置的话可能不安全。 |
😂这个应该需要注入react和vue,会直接过滤掉XSS注入的风险 |
只要可以注入就可以执行 js 吧? |
React和Vue都做了XSS过滤,本质上是先转化为虚拟DOM,再转化成HTML |
是哦,太久没用都忘记了,是要开启个啥 tpl 才可以注入,但是在生命周期里面可以直接写各种脚本,这些是可以被执行的吧。 |
那就是应该可以被执行的,确实有这个问题。应该可以再加一个开启特性的开关,这样避免三方网站被用户注入 |
用插件或者开关都可以。前面你说的重构可以考虑用插件或许更好一点。 |
是的,渲染 markdown 的话,插件化是最好的解决方案。 如果要重构部分代码的话,得先从样式表重构开始。针对于渲染 现在插件的探索,在链滴里面写过一些思路,更深层的特性实现可能需要与 lute 进行沟通了 为了适配进一步插件化的话,Vditor 的 类型定义 得独立为一个包发布了,这样社区才能复用此部分类型定义。采用 monorepo 还是独立仓库管理,这个得看实际情况了。 emmmm 或许建一个 office team 仓库来维护 Vditor 相关的包,我感觉是非常值得的,就跟现在主流的开源项目管理一样。 |
插件的定义在这个项目中 https://github.com/HerbertHe/vditor-plugin 对于 Lute 支持的 js 自定义 renderers 情况,重写了部分的类型定义 |
稍后拜读下 |
/间隔近两年,我遇到了这样的需求, 无意搜索到楼主的issue,感觉我懂你的意思。 封装的渲染富文本的组件: RenderRtf.tsx import { useState, useEffect, useRef } from "react";
import parse from "html-react-parser";
import ReactDOM from "react-dom/client";
import Hello from "./Hello";
// 这里注册富文本中包含的自定义组件(也可以外部注册)
const regCompPublic = {
Hello,
};
// 渲染富文本
const RenderRtf = (props) => {
// 组件名字处理
const regCompTemp = { ...regCompPublic, ...props["regComp"] };
const regComp = {};
for (const key in regCompTemp) {
const keyStr = key.toLowerCase();
regComp[keyStr] = regCompTemp[key];
}
// 子实例的根节点
const [root, setRoot] = useState<any>();
// 子实例的根节点的挂载点
const wrappNode = useRef(null);
// 子实例渲染
const rootRender = () => {
root.render(
parse(props.content, {
replace(domNode: any) {
if (Object.keys(regComp).indexOf(domNode.name) > -1) {
const Cmp = regComp[domNode.name];
return <Cmp {...domNode.attribs} />;
} else {
return domNode;
}
},
})
);
};
// 一旦有子实例了,就立刻渲染,传入的内容更改 也需要重新渲染
useEffect(() => {
root?rootRender():console.warn("没有实例对象,此次实例无法重新render")
}, [root, props]);
// 挂载第二(子)实例
useEffect(() => {
if (wrappNode.current) {
const instanceRoot = ReactDOM.createRoot(wrappNode.current);
setRoot(instanceRoot);
}
}, [wrappNode]);
return (
<>
<div className="wrapp" ref={wrappNode}></div>
</>
);
};
export default RenderRtf; 页面中使用: App.tsx import { useState } from "react";
import RenderRtf from "./components/RenderRtf";
import MarkdownIt from "markdown-it";
const md = MarkdownIt({
html: true,
linkify: true,
typographer: true,
});
function App() {
const [ipt, setIpt] = useState("你好 <Hello/>");
const onInput = (e: any) => {
setIpt(e.target.value);
};
return (
<>
<textarea value={ipt} onInput={onInput}/>
<RenderRtf content={md.render(ipt)} />
</>
);
}
export default App; 基于此,vue我也实现了 <script setup lang="ts">
import { onMounted, ref, watch } from "vue";
import Hello from "@/components/HelloWorld.vue";
import * as Vue from "vue/dist/vue.esm-bundler.js";
const props = defineProps(["content"]);
let app: any = null;
const renRender = () => {
if (!showRtf.value) {
console.warn("没有子实例对象的挂载真实node,此次实例无法重新render");
return false;
}
// 不同于react,vue对外没有暴漏render函数,所以无法手动调用,只能更新整个实例来渲染
if (app) {
app.unmount();
}
showRtf.value.innerHTML = props.content;
app = Vue.createApp({
components: { //这里组测富文本中包含的vue组件
Hello,
},
});
app.mount(showRtf.value);
};
onMounted(() => {
renRender();
});
watch(
() => props.content,
(newVal) => {
renRender();
}
);
const showRtf = ref();
</script>
<template>
<div ref="showRtf"></div>
</template> 这里是使用 App.vue <script setup lang="ts">
import {createApp} from 'vue';
import { ref, watch, computed } from "vue";
import MarkdownIt from "markdown-it";
import RenderRtf from '@/components/render-rtf.vue';
const md = MarkdownIt({
html: true,
linkify: true,
typographer: true,
});
const ipt = ref("哈哈<Hello/>");
</script>
<template>
<textarea v-model="ipt"></textarea>
<render-rtf :content="md.render(ipt)"/>
</template> 其本质原理,都是多实例。 |
@dingshaohua-cn 哈哈哈我自己都快忘了当时提的是啥 issue 了,你这个实现方法我看懂了,就跟 vuepress 他们对组件渲染的做法是一样的,本质上还是将 markdown 转化成 react/vue 的组件进行渲染,等于是外面再套了一层 react/vue。实际上我当时提这个 issue 的想法,还不是在外面包 react/vue,而是在里面包 react/vue 的渲染,也是二次渲染。但是原理不太一样,是从直接实现 vditor 支持 react、vue、svelte 等所有的框架的组件针对特定区块进行渲染。 其过程应该是 具体实现应该参考:https://cn.vuejs.org/guide/quick-start.html#using-vue-from-cdn 通过 iife 直接捕获渲染,跟 vditor 渲染 mermaid 是一个道理。 |
所以后来我考虑去直接设计支持 vditor plugin 了,支持直接重写渲染器。看了 vditor 的源码,无异于重写整个项目,所以放弃了,转而通过 markdown-it 去写 markmax,重写了 markdown-it 的 renderers ,并且通过转化 millionjs 的 AST 可以实现 diff。不过生活所迫,烂尾了哈哈哈哈哈哈 |
vditor在渲染频率上还是有问题的,编辑器定时器调低了 SV 渲染会卡顿的,后来写 markmax 就是希望通过 vdom 来解决这个问题。直接干掉定时器的设定,进行局部更新,当时直接 vdom 就可以 diff 进行局部渲染,不用重绘所有的节点,毕竟markdown 渲染这也是一个痛点所在。 |
你在什么场景下需要该功能?
越来越多的 Markdown 渲染器支持 React 和 Vue 组件的渲染,虽然渲染的实现方式都不太一样。在如今版本的 Vditor 中,可以类似于 abcjs、mermaid 支持的方式来支持 Vue 和 React 的单组件渲染,引入对应的 CDN。
描述最优的解决方案
88250/lute#164
可以通过如下的方式,便于集成外部组件引入
描述候选解决方案
还有更多的解决方案,依赖于对 https://github.com/vitejs/vite 进行集成,采用的思路于 VuePress 等类似。更适用于项目工程开发集成,需要处理 Vite 运行时和 Vditor 运行时的兼容。不过可以省略上面 CDN 的引入,并且可以同时兼容 Vite 生态几乎所有的适用插件。
其他信息
The text was updated successfully, but these errors were encountered: