VuePress Toc 组件实现方案

记 VuePress RC20 更新后,官方 toc 插件报错替代方案
发布于
本文导览

VuePress Toc 组件实现方案

近期 VuePress V2 更新到 RC20版本后,构建时会产生如下报错:

 Initializing and preparing data - done in 3.53s
 Compiling with vite - done in 4.27s
 Rendering 78 pages - failed in 59ms
error Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@internal/routes' imported from C:\Users\Admin\WebstormProjects\WuShu\node_modules\@vuepress\client\dist\chunk-TET3OVHF.js
    at new NodeError (node:internal/errors:406:5)
    at packageResolve (node:internal/modules/esm/resolve:789:9)
    at moduleResolve (node:internal/modules/esm/resolve:838:20)
    at defaultResolve (node:internal/modules/esm/resolve:1043:11)
    at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:383:12)
    at ModuleLoader.resolve (node:internal/modules/esm/loader:352:25)
    at ModuleLoader.getModuleJob (node:internal/modules/esm/loader:228:38)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:85:39)
    at link (node:internal/modules/esm/module_job:84:36)

经过逐一排查,发现问题在于这个@vuepress/[email protected]这个toc插件中。这个插件在本站提供了文章页的文章目录导航的功能。 虽然预期会很快修复,但是我不太喜欢这种不确定性,另外感觉实现并不复杂,于是着手进行改造替换。 更新:前一天已发布@vuepress/[email protected],估计是对这个问题进行了解决,但我没有重新进行测试。

构建思路

因为之前已经根据 toc 插件生成的目录树结构,构建了目录指示条,以及对样式、activeHeaderLinksPlugin 插件进行了优化,所以本次进行替换尽量保持和原来的 html 结构保持一致。 同时,因为本站文章小标题只采用二、三级标题,因此只简化生成此两类标题目录,最终模板部分代码如下:

<nav class="vuepress-toc">
    <ul class="vuepress-toc-list">
        <li
                v-for="item in headers"
                :key="item.slug"
                class="vuepress-toc-item"
        >
            <a
                    class="route-link vuepress-toc-link"
                    :class="{'asideActive': hashPos === item.link, 'active': hasActiveChild(item)}"
                    :href="item.link"
                    :aria-label="item.title"
            >
                {{ item.title }}
            </a>
            <ul class="vuepress-toc-list"
                v-if="hasChildren(item)"
            >
                <li v-for="child in item.children" :key="item.slug" class="vuepress-toc-item">
                    <a
                            class="route-link vuepress-toc-link"
                            :class="{'asideActive': hashPos === child.link, 'active': hashPos === child.link}"
                            :href="child.link"
                            :aria-label="child.title"
                    >
                        {{ child.title }}
                    </a>
                </li>
            </ul>
        </li>
    </ul>
</nav>

javascript 代码如下:

import {useRoute} from "vue-router";
import {ref, watch} from "vue";

// 当前激活项目,以及用来判断是否有子项目被激活
const route = useRoute();
let hashPos = ref()
watch(
    () => route.hash,
    (newv, oldv) => {
        hashPos.value = newv;
    }, {immediate: true}
);

// 用来传入文章headers信息
defineProps({
    headers: {
        type: Array,
        required: true
    }
})

// 判断是否有子项目(三级标题)
const hasChildren = (item) => {
    return item.children?.length > 0
}

// 递归检查是否存在激活子项
const hasActiveChild = (item) => {
    const checkChildren = (children) => {
        return children.some(child => {
            return child.link === hashPos.value ||  // 当前子项激活
                (child.children && checkChildren(child.children))  // 递归检查深层子项
        })
    }
    return item.children ? checkChildren(item.children) : false
}

使用方式

在需要使用的页面导入该组件:

import Toc from "../components/Toc.vue";

并在模板中使用即可:

<ClientOnly>
    <Toc :headers="PageData.headers"/>
</ClientOnly>

因为涉及到内容更新,为了避免报 app-DoG9RpcD.js:15 [Vue warn]: Hydration class mismatch 错误,可以在外围加上 ClientOnly 标签。

评论
加载评论模块