如何用vite的vueCustomBlockTransforms(自定义块转换)实现源代码展示

想实现源代码展示,搜索结果90%都是用vueCustomBlockTransforms,但我在自己项目的vite.config.js里加入该选项以后并没有生效
在vite官网配置中也没有,vite源码中没有(但是node/server的createServer中有)
找到了一个demo,也是可以用的(虽然vite版本是很早的1.0):https://zhuanlan.zhihu.com/p/344701005
就很奇怪?奇怪一天多了。

---- 230523更新 ----

终于搞明白了怎么用,还是得看官方文档(泪目
vueCustomBlockTransforms不能使用的原因是,在网上看到的几乎所有示例用的vite版本都是1.x,而从2开始的版本,所有 Vue 特定选项都已移除,vueCustomBlockTransforms也无效了。
官方给出了新版本处理自定义块的写法:vite版本迁移指南-自定义块转换
(上面的地址我后来试好像打不开,这样的话可以从Vite官网点进去 → 开始 → 从v1迁移 → 下拉找到自定义块转换)
以此为参考,用标签展示源代码的例子可以写为:

代码

vite.config.js

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import fs from "fs"; // node自带,不需要安装依赖
import { baseParse } from "@vue/compiler-core";

const vueDemoPlugin = {
	name: "vue-block-demo",
	transform(code, path) {
		if (!/vue&type=demo/.test(path)) {
			return;
		}
		const filePath = path.split("?")[0];
		//异步读取文件内容,并转为string类型
		const file = fs.readFileSync(filePath).toString();
		//将读取到的文件中的自定义快渲染为AST
		const parsed = baseParse(file).children.find((n) => n.tag === "demo");
		//读取自定义模块中的文本内容
		const title = parsed.children[0].content;
		//将读取文件中的自定义块切分,并转为字符串类型
		const main = file.split(parsed.loc.source).join("").trim();
		//以JSON数据类型返回
		return `export default Comp => {
			Comp.__sourceCode = ${JSON.stringify(main)}
			Comp.__sourceCodeTitle = ${JSON.stringify(title)}
		}`;
	},
};

export default defineConfig({
	plugins: [
		vue(),
		vueDemoPlugin,
	],
});

CodeDemo.vue

<demo>xxx </demo>
<template>
	<div>你好</div>
</template>

<!-- <script>
export default {
	setup() {
		return {};
	},
};
</script> -->

CodeView.vue

<template>
	<div class="demo">
		<h2>{{ compTitle }}</h2>
		<div class="demo-component">
			<component :is="component" />
		</div>
		<div class="demo-actions">
			<el-button v-if="codeVisible" @click="toggleCode">隐藏代码</el-button>
			<el-button v-else @click="toggleCode">查看代码</el-button>
		</div>

		<div :class="'demo-code' + [codeVisible ? ' code-show ' : ' code-hidden ']">
			<pre class="language-html" v-html="html" />
		</div>
	</div>
</template>

<script setup>
import "prismjs"; // 需要安装prismjs依赖(用于美化代码)
import "prismjs/themes/prism.css";
const Prism = window.Prism;
const props = defineProps({
	component: {
		type: Object,
		required: false,
		default: function () {
			return {};
		},
	},
});

let html = ref("");
let compTitle = ref("");
const createHtml = (comp) => {
	compTitle.value = comp.__sourceCodeTitle;
	html.value = Prism.highlight(comp.__sourceCode, Prism.languages.html, "html");
};

onMounted(() => {
        // 因为我传入的组件是异步引入的,所以设置了250毫秒延时后在__asyncResolved中才能拿到组件信息,如果直接用import方式引入的话直接用props.component就可以,也不需要延时
	setTimeout(() => {
		const resolvedComp = props.component.__asyncResolved;
		if (resolvedComp) {
			createHtml(resolvedComp);
		}
	}, 250);
});

const toggleCode = () => (codeVisible.value = !codeVisible.value);
const codeVisible = ref(false);
</script>

引用CodeView组件的父级组件

<template>
	<div>
		<code-view :component="CodeDemo"></code-view>
	</div>
</template>

<script setup>
import { defineAsyncComponent } from "vue";
import CodeView from "@/components/common/CodeView.vue";
const CodeDemo = defineAsyncComponent(() => import("@/components/common/CodeDemo.vue")); // 这里用了异步引入组件
// import CodeDemo from "@/components/common/CodeDemo.vue"; // 也可以用这种方式引入,在CodeView中获取源码和标题时写法会有区别
</script>

效果

效果如下(是的注释的内容也能看到):

避免XSS攻击

ps. 用vue-dompurify-html避免v-html的XSS攻击漏洞:

 npm i vue-dompurify-html

main.js

import { createApp } from "vue";
import VueDOMPurifyHTML from "vue-dompurify-html";
const app = createApp(App);
app.use(VueDOMPurifyHTML);
app.mount("#app");

使用:替换原来的v-html

<!-- <pre class="language-html" v-html="html" /> -->
<pre v-dompurify-html="html" class="language-html" />
posted @ 2023-05-16 16:49  宇宙野牛  阅读(424)  评论(0编辑  收藏  举报