Skip to content

VitePress 实现短链接分享(旧版) ​

WARNING

本文是已停用架构的历史记录,文中代码不再与当前仓库同步。当前实现见 短链接分享。

基本逻辑 ​

短链接格式为 https://notes.linho.cc/s?q=xxxxxxxxxx,其中 ID 是文件路径去掉 .md 或 index.md 后 MD5 的前 10 位。构建时生成根目录 shortmap.json,浏览器打开 /s 后请求该文件、查找目标路径并完成跳转。

整个流程是:

  1. 分享组件根据当前页面路径计算 10 位十六进制 ID。
  2. VitePress buildEnd 钩子为所有页面生成 ID—路径映射表。
  3. /shortUrl.md 通过 rewrites 映射到 /s。
  4. 跳转组件使用 axios 请求 /shortmap.json,再调用 VitePress Router 跳转。

构建阶段:生成映射表 ​

ts
import md5 from "blueimp-md5";
import fs from "fs";
import type { SiteConfig } from "vitepress";

export default async function mapShortUrl(siteConfig: SiteConfig) {
  const shortMap: Record<string, string> = {};
  siteConfig.pages.forEach((path) => {
    path = path.replace(/(index)?\.md$/, "");
    shortMap[md5(path).slice(0, 10)] = path;
  });
  fs.writeFileSync(
    `${siteConfig.outDir}/shortmap.json`,
    JSON.stringify(shortMap),
  );
}

生成结果大致如下:

json
{
  "9f5ef1467f": "C-C++ 相关/C++ Primer/",
  "6c84242693": "C-C++ 相关/C++ Primer/第1章 开始",
  "41efd5a73a": "C-C++ 相关/C++ Primer/零散的笔记素材"
}

然后在 config.mts 的 buildEnd 中调用它:

ts
export default {
  buildEnd: (siteConfig) => {
    mapShortUrl(siteConfig);
  },
};

客户端:查表与跳转 ​

旧跳转组件从 query string 中取出 10 位 ID,再请求映射表:

ts
import axios from "axios";
import { useRouter } from "vitepress";
import { onMounted } from "vue";

const router = useRouter();
onMounted(() => {
  const id = window.location.search.match(/\?q=(.{10})$/)?.[1];
  if (!id) return router.go("./404");
  axios.get("/shortmap.json").then(
    (res) =>
      res.data[id] !== undefined
        ? router.go(`./${encodeURI(res.data[id])}`)
        : router.go("./404"),
    () => router.go("./404"),
  );
});

客户端:分享组件 ​

生成分享 URL 时不需要查表,只要使用同一套路径归一化和 MD5 算法:

ts
watchEffect(() => {
  const path = page.value.filePath.replace(/(index)?\.md$/, "");
  if (encodeURI(path).length < 10)
    link.value = `${baseUrl}/${encodeURI(path)}`;
  else link.value = `${baseUrl}/s?q=${md5(path).slice(0, 10)}`;
});

存在的问题 ​

编码空间利用率低:10 位十六进制字符只使用 0-9a-f,共提供 编码空间。当前版本改用 8 位 Base36,在规避大小写文件名问题的同时提供约 编码空间。

映射表可能与页面产物缓存错配:shortmap.json 位于输出根目录且文件名不带内容哈希。在 GitHub Pages 的缓存窗口内,新页面可能与旧映射表同时出现。

额外的运行时请求:每次打开短链都必须先加载 VitePress 页面,然后再请求整张 JSON 映射表。为这一次简单请求引入 axios 也没有必要。

无法提供链接元数据:/s 的静态 HTML 不知道最终目标页面,大多数社交平台又不执行页面 JavaScript,因此只能抓取到统一的加载页或空元数据。

因此后来换用了新方案,详见新版文档 短链接分享。

相关文档 ​