Skip to content

vitepress

vitepress 探索

几种引入代码的方式

1 导入代码片段(推荐)

markdown
<<< ./index.js#snippet
js
// #region snippet
export function foo (value) {
  ...
}
// #endregion snippet

2 引入整个文件

js
// 导入markdown,整合markdown很有用
`<!--@include: ./***.md-->`
// 导入markdown,局部内容
`<!--@include: ./parts/basics.md{3,10}-->`
// 导入代码片段不适用
`<!--@include: ./***.js-->`

3 使用vue引入(暂不推荐)

js
// index.js
export function foo (value) {
  ...
}
markdown
<!-- markdown -->
<script setup>
import {foo} from "./index.js"
</script>
<!-- 不是代码片段 -->
<pre><code class="language-javascript">{{ foo.toString() }}</code></pre>
<!-- 代码不高亮 -->
\`\`\`bash-vue
{{ foo }}
\`\`\`

参考:导入代码片段

动态引入

vite markdown configImport code snippets as dynamic path #3334MarkdownOptions 场景:

  • 动态加载.md文件 解决思路:
  • 1、对<!--@include: (.*?)-->进行重写覆盖
js
// vite.config.js
import { defineConfig } from 'vite';
import fs from 'fs';
import path from 'path';

export default defineConfig({
  plugins: [
    {
      name: 'vite-plugin-md-include',
      transform(src, id) {
        if (id.endsWith('.md')) {
          return src.replace(/<!--@include: (.*?)-->/g, (match, p1) => {
            const filePath = path.resolve(path.dirname(id), p1.trim());
            if (fs.existsSync(filePath)) {
              return fs.readFileSync(filePath, 'utf-8');
            } else {
              console.warn(`File not found: ${filePath}`);
              return ''; // 如果文件不存在,返回空字符串
            }
          });
        }
      }
    }
  ]
});
  • 2、动态加载数据,markdown-it解析,vue渲染
  • github markdown
markdown
<!-- 动态加载 github markdown 数据 -->
<script setup>
import { ref, onMounted } from 'vue';
import markdownit from 'markdown-it'
const md = markdownit()
const markdownContent = ref('');
onMounted(async () => {
  try {
    const response = await fetch('https://raw.githubusercontent.com/nzakas/understandinges6/master/manuscript/00-Introduction.md');
    if (response.ok) {
      const value = await response.text();
      markdownContent.value = md.render(value);
    } else {
      console.error('Failed to fetch markdown file');
    }
  } catch (error) {
    console.error('Error fetching markdown file:', error);
  }
});
</script>
<div v-html="markdownContent"></div>
  • 本地 markdown
js
// index.data.js
// 动态加载 本地 markdown 数据
import fs from 'node:fs'
import markdownit from 'markdown-it'
const md = markdownit()
// 在这里可以给 markdownit 添加代码高亮
export default {
  watch: ['./md/*.md'],
  load(watchedFiles) {
    return watchedFiles.sort((pre,next)=>{
      return pre.match(/(\d+)、/)[1] - next.match(/(\d+)、/)[1]
    }).map((file) => {
      return  md.render(fs.readFileSync(file, 'utf-8'))
    })
  }
}
markdown
<!--
1、解析出来的markdown 文档与vitepress基本一致
2、代码片段没有高亮 :可以尝试添加高亮组件
-->
<script setup>
  import { data } from './index.data.js'
</script>

<div v-for="item in data" v-html="item"></div>
  • 3、动态加载数据,marked解析,vue渲染
js
import fs from 'node:fs'
export default {
  watch: ['./md/*.md'],
  load(watchedFiles) {
    return watchedFiles.sort((pre,next)=>{
      return pre.match(/(\d+)、/)[1] - next.match(/(\d+)、/)[1]
    }).map((file) => {
      return  fs.readFileSync(file, 'utf-8')
    })
  }
}
markdown
<!-- 
1、marked 解析出来的markdown文档 与vitepress不一致,蓝色文字,theme需要调整
2、增加代码高亮,代码可以高亮,拷贝插件无法显示
3、改在md中解析md,CopyButtonPlugin在nodejs报错,找不到document
 -->
<script setup>
  import { data } from './dp.data.js'
  import hljs from 'highlight.js';
  import { Marked } from "marked";
  import { markedHighlight } from "marked-highlight";
  import CopyButtonPlugin from "highlightjs-copy"
  import javascript from 'highlight.js/lib/languages/javascript';
  import hljsVuePlugin from "@highlightjs/vue-plugin";
  const copyPlugin = new CopyButtonPlugin({
    hook: (text, el) => {
      if (el.dataset.copyright) {
        return `${text}\n\n${el.dataset.copyright}`;
      }
      if (el.dataset.highlight) {
        el.style.filter = "saturate(5)";
      }
      if (el.dataset.replace) {
        return text
          .replace("{{name}}", "Alexander Graham Bell")
          .replace("{{dog}}", "Platypus");
      }
      return text;
    },
  })
  hljs.addPlugin(copyPlugin);
  hljs.highlightAll()
  const highlightjs = hljsVuePlugin.component

  const marked = new Marked(
    markedHighlight({
      emptyLangClass: 'hljs',
      langPrefix: 'hljs language-',
      highlight(code, lang, info) {
        const language = hljs.getLanguage(lang) ? lang : 'plaintext';
        const val = hljs.highlight(code, { language }).value;
        return val
      }
    })
  );

  const dataHtml = data.map(item=> marked.parse(item))
</script>
<span>1 highlightjs</span>
<highlightjs language="js" code="console.log('Hello World');"></highlightjs>
<span>2 code class="hljs language-js"</span>
<pre><code class="hljs language-js">console.log('Hello world!');</code></pre>
<span>3 code class="hljs language-javascript"</span>
<pre><code class="hljs language-javascript">console.log('Hello world!');</code></pre>

<div v-for="val in dataHtml">
  <code class="hljs-html" v-html="val"></code>
</div>
  • 4、动态生成文件名称
js
export default {
  // 相对路径
  // watch: ['../../../../../front-end/DesignPatterns/md/*.md'],
  // 根目录路径
  // watch: ['docs/front-end/javaScript/ES6/md/*.md'],
  watch: ['docs/front-end/DesignPatterns/md/*.md'],
  // watch: ['docs/back-end/Lang/NodeJs/Book/note/**/*.md'],
  load(watchedFiles) {
    const files = watchedFiles.sort((pre,next)=>{
      const preNum = pre.match(/(\d+)[、-]?/)
      const nextNum = next.match(/(\d+)[、-]?/)
      if(preNum === null || nextNum===null)return 0
      return preNum[1] - nextNum[1]
    })
    // .map(item => {
    // // 相对路径 => ./md/${文件名}
    //   const path = item.match(/\/([^\/]+)$/)[1]
    //   return  `<!--@include: ./md/${path}-->`.replaceAll(/\\/g,'/')
    // })
    .map(item => {
    // 根路径 => ./md/${文件名}
      const path = item.replace("docs","@")
      return  `<!--@include: ${path}-->`.replaceAll(/\\/g,'/')
    })
    return files
  },
}

alias 路径别名

  • alias
    • /.vitepress/config.mts 中配置alias只对前端部分的开发、打包有效
    • /.vitepress/config.mts 中配置alias只对前端vue组件import有效,对于代码片段<<< @/**/ */,无效,@/是vitepress自带的,配置不能生效
      js
      import { defineConfig } from 'vitepress'
      import {fileURLToPath} from 'node:url'
      // import { resolve } from 'node:path';
      export default defineConfig({
        vite: {
          resolve: {
            alias: [
              // new URL('../../docs', import.meta.url) 使用import.meta.url了,所有路径是相对于当前这个文件的
              { find: '@', replacement: fileURLToPath(new URL('../../docs', import.meta.url).href)},
            ]
            // 或者
            // alias:{
              // '@': resolve(__dirname, '../../docs'),
            // }
          },
        },
      })
  • 在 tsconfig.json paths 配置 "@/*": ["./docs/*"]
  • vitepress dev docs 启动以后
    • *.data.js是属于nodejs服务端,使用alias, 无效
    • 在 package.json 中 "dev": "vitepress dev docs",./docs目录下就是根目录
    • *.data.js中引用其他模块,可采用绝对路径,tsconfig.json paths配置 "/*": ["./docs/*"]配合绝对路径
    • *.data.jsimport name from '/utils/**.mts',/utils就是docs根目录下文件夹

在vitepress中引入.html文件

1、vite 插件

  • import index from './index.html'需要vite 插件处理

    js
    // vite-plugin-html-string.js
    import fs from 'node:fs';
    import path from 'node:path';
    
    function htmlString() {
      return {
        name: 'html-string',
        transform(code, id) {
          if (id.endsWith('.html')) {
            const filePath = path.resolve(id);
            const htmlContent = fs.readFileSync(filePath, 'utf-8');
            // console.log(htmlContent)
            return {
              code: `export default ${JSON.stringify(htmlContent)};`,
              map: null,
            };
          }
        },
      };
    }
    
    export default htmlString;
  • import() 动态导入,但是import只引入esm

  • v-html 解析

    vue
    
    <template>
      <div v-html="content"></div>
    </template>
    <script setup>
    import { ref, onMounted } from 'vue';
    const props = defineProps({
      html: {
        type: String,
        required: true
      }
    })
    const content = ref(null);
    onMounted(() => {
      content.value = props.html;
    });
    </script>
    vue
    <script setup>
    import htmlContent from './index.html'
    import VHtml from '@/components/RenderHtml/VHtml.vue'
    </script>
    <template>
      <VHtml :html="htmlContent"></VHtml>
    </template>
    • 问题:会将 header、body、html标签过滤掉
  • 使用 shaowDom

    vue
    <template>
      <div ref="shadowHost"></div>
    </template>
    <script setup>
    import { ref, onMounted } from 'vue';
    const props = defineProps({
      html: {
        type: String,
        required: true
      }
    })
    const shadowHost = ref(null);
    onMounted(() => {
      const shadow = shadowHost.value.attachShadow({ mode: 'open' });
      shadow.innerHTML = props.html;
    });
    </script>
    vue
    <script setup>
    import htmlContent from './index.html'
    import ShadowDom from '@/components/RenderHtml/ShadowDom.vue'
    </script>
    <template>
      <ShadowDom :html="htmlContent"></ShadowDom>
    </template>
    • 问题:同v-html,会将 header、body、html标签过滤掉

2、不处理文件,直接引用

  • fatch 获取

    vue
    <template>
      <div v-html="htmlContent"></div>
    </template>
    
    <script setup>
    import { ref, onMounted } from 'vue';
    
    const htmlContent = ref('');
    const props = defineProps({
      url: {
        type: String,
        required: true
      }
    })
    onMounted(async () => {
      try {
        const response = await fetch(props.url);
        // debugger
        htmlContent.value = await response.text();
        console.log(htmlContent.value)
      } catch (error) {
        console.error('Error fetching HTML file:', error);
        htmlContent.value = '<p>Failed to load HTML content.</p>';
      }
    });
    </script>
    vue
    <script setup>
    import Fetch from '@/components/RenderHtml/Fetch.vue'
    </script>
    <template>
      <!-- url传入一个相对路径,会有路径的问题 -->
      <Fetch url="./index.html"></Fetch>
      <!-- 使用new URL(src, import.meta.url).href,但是在md(html)中不允许使用new URL,只可以在script中使用-->
      <Fetch :url="new URL(src, import.meta.url).href"></Fetch>
    </template>
    • 路径问题:
      • 因为vitepress启动服务器,直接引入.html路径是已经处理过的,找不到对应文件
      • 传入的路径是相对与当前文件的,但是传给Fetch组件之后,就要相于Fetch组件了
      • 需要将文件放置到public下
      • 无论使用 v-html 还是 shadowDom,都会将 header、body、html标签过滤掉
  • iframe

    • 直接使用public下路径的文件
      html
      <iframe src="/index.html" width="100%" height="100%"></iframe>
    • 解决路径问题,也解决了header、body、html标签过滤掉的问题
    • 问题:drawio 导出的.html 文件,有查看大图的操作,内嵌iframe,查看大图会单独打开一个页面
    • 解决:不引入.html,改为引入img/svg,使用vue插件 v-viewer显示查看大图

vitepress 引入vue插件

1、在./vitepress/theme/index.js中引入vue插件

ts
import { EnhanceAppContext } from 'vitepress'
import Viewer from 'v-viewer'
import { directive as viewer } from "v-viewer"
import DefaultTheme from 'vitepress/theme'
Viewer.setDefaults({
  // zoomable: false,
  zoomRatio: 1.2,
})
export default {
  ...DefaultTheme,
  enhanceApp({ app, router, siteData }: EnhanceAppContext) {
    app.directive('viewer', viewer());
    // app.use(Viewer);
  }
}
  • 2、在组件中使用
vue
<template>
  <img v-viewer src="https://picsum.photos/200/200" />
</template>

vite press 增加标签

vitepress 插件