如何用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" />