Skip to content
On this page
字数:1.2k 字
预计:5 分钟
阅读量:

Vitepress 开发问题记录

作者:winches
更新于:7 个月前

给Vitepress快速添加全局搜索

一、用内置的local搜索功能

vitepress的配置里加上这一段

json
{
  "themeConfig": {
    "search": {
      "provider": "local",
      "options": {
        "translations": {
          "button": { "buttonText": "搜索文档", "buttonAriaLabel": "搜索文档" },
          "modal": {
            "noResultsText": "无法找到相关结果",
            "resetButtonTitle": "清除查询条件",
            "footer": {
              "selectText": "选择",
              "navigateText": "切换",
              "closeText": "关闭"
            }
          }
        }
      }
    }
  }
}

随后出现的效果如图:

image

二、使用社区的插件vitepress-plugin-search 或者vitepress-plugin-pagefind

vitepress-plugin-search为例

安装依赖

bash
pnpm add -D vitepress-plugin-search flexsearch

vite配置中添加插件

ts
import { SearchPlugin } from 'vitepress-plugin-search'

const searchOptions = {
  previewLength: 62,
  buttonLabel: 'Search',
  placeholder: 'Search docs',
}

export default {
  vite: {
    plugins: [
      SearchPlugin(searchOptions),
    ],
  },
}

效果图:

image

给Vp页面加上访问量记录

方案一、使用busuanzi服务

实现过程

  1. 安装busuanzi.pure.js
shell
pnpm add -D busuanzi.pure.js
  1. 在页面路由变化的时候,调用busuanzi服务
ts
export default {
  enhanceApp: ({ router }: EnhanceAppContext) => {
    router.onAfterRouteChanged = () => {
      isClient && busuanzi.fetch()
    }
  },
}
  1. 通过md编译插件为每个页面添加上阅读量组件

  2. 添加busuanzi_value_page_pv唯一标识至标签中

html
阅读量:<span id="busuanzi_container_page_pv"><span id="busuanzi_value_page_pv" /></span>

实现原理:

当页面路由变化后,在变化后的页面发送busuanzi的请求,并通过jsonp回调函数执行,添加访问量到ID中,完成访问量的记录。

常见问题

  1. busuanzi.fetch()调用前,需要判断是否是客户端,否则Node打包会报错。
  2. referrer默认是strict-origin-when-cross-origin,需更改referrer策略,以达到发送网站信息,准确记录网站访问量
  • 比如:unsafe-urlno-referrer-when-downgrade(推荐)

注意:

strict-origin-when-cross-origin 是一个CSP(Content Security Policy)的策略指令,用于指定浏览器在跨域请求时如何发送Referer头信息。当浏览器从一个站点跳转到另一个站点时,Referer头信息会告诉目标站点请求来自哪个站点。这个指令的作用是在跨域请求时,只有在目标站点和源站点的协议、主机名和端口号都相同时,会发送Referer头信息,否则不发送。

例如,如果当前页面的URLhttps://example.com/page1.html,并且它包含一个指向https://example.net/page2.html 的链接,那么如果链接中包含 rel="noreferrer" 属性或没有设置 rel 属性,那么在跳转到 https://example.net/page2.html 时,Referer头信息将不会包含 https://example.com/page1.html 这个信息。但是,如果在当前页面的HTTP应头中设置了 Referrer-Policy:strict-origin-when-cross-origin,那么在跨域请求时,只有当目标站点和源站点的协议、主机名和端口号都相同时,才会发送Referer头信息。 这个指令可以帮助保护用户的隐私,因为它可以防止目标站点获取到来自其他站点的Referer头信息,从减少了跨站点追踪的可能性。

方案二、Visitor Badge

使用方法

点击查看文档

react-docgen-typescript 解析问题

当使用docgen.parse时,若加了displayName那么只会识别并返回对应的组件,并更改解析后的displayName为相应的值

若不指定displayName则返回全部export的组件

Rspress 开发问题记录

自动识别组件 Props 生成类型文档

  • 使用react-docgen-typescript + remark 插件实现
ts
import { existsSync } from 'node:fs'
import * as docgen from 'react-docgen-typescript'
import { visit } from 'unist-util-visit'
import { resolver } from '../helpers/path'
import { PropsOptions } from './Props'

const options: docgen.ParserOptions = {
  savePropValueAsString: false,
  propFilter: {
    skipPropsWithoutDoc: false,
  },
}

interface NodeAttributes {
  name: keyof PropsOptions
  value: string
}

export function remarkPropsTable() {
  return (tree) => {
    visit(tree, 'mdxJsxFlowElement', (node, index, parent) => {
      if (node.name === 'Props') {
        const src = node.attributes.find((attr: NodeAttributes) => attr.name === 'src')?.value

        if (!src)
          return

        const component = node.attributes.find((attr: NodeAttributes) => attr.name === 'component')?.value

        const codeSrc = resolver(src)

        if (existsSync(codeSrc)) {
          const componentsMetaJson = component
            ? docgen
              .parse(codeSrc, options)
              .find(c =>
                c.displayName.toLocaleLowerCase().includes(component.replace('.mdx', '').toLocaleLowerCase()),
              )
            : docgen.parse(codeSrc, options)[0]

          if (componentsMetaJson) {
            const isOverLength = Object.keys(componentsMetaJson.props).length >= 20

            const markdown = generateTable(componentsMetaJson, isOverLength)

            if (markdown)
              parent.children.push(markdown)

          }
        }
      }
    })
  }
}

interface Prop {
  defaultValue: unknown
  description: string
  name: string
  required: boolean
  type: {
    name: string
  }
}

interface ComponentMetadata {
  displayName: string
  props: Record<string, Prop>
}

const baseHTMLAttributesRegex = /aria-*|data-*|tabIndex|role/
const attributesRegex
  = /^on[A-Z]|dir|draggable|hidden|id|lang|nonce|slot|spellCheck|style|title|translate|autoCapitalize|autoCorrect|autoSave|itemProp|itemScope|itemType|itemID|itemRef|itemGroup|itemValue|itemProp|about|inlist|content|prefix|property|rel|resource|vocab|rev|typeof|color|results|security|unselectable|is|inputMode|dangerouslySetInnerHTML|defaultChecked|defaultValue|suppressContentEditableWarning|suppressHydrationWarning|accessKey|contentEditable|contextMenu|autoFocus|radioGroup/

function generateTable(metadata: ComponentMetadata, isOverLength: boolean) {
  const table = {
    type: 'table',
    align: ['left', 'center', 'center', 'center'],
    children: [
      {
        type: 'tableRow',
        children: [
          { type: 'tableCell', children: [{ type: 'text', value: 'Property' }] },
          { type: 'tableCell', children: [{ type: 'text', value: 'Type' }] },
          { type: 'tableCell', children: [{ type: 'text', value: 'Required' }] },
          { type: 'tableCell', children: [{ type: 'text', value: 'Description' }] },
        ],
      },
      ...Object.entries(metadata.props)
        .map(([propName, prop]) => {
          if (isOverLength) {
            // 过滤掉没有描述的 HTML 属性,和基础的HTML属性
            if ((!prop.description && attributesRegex.test(prop.name)) || baseHTMLAttributesRegex.test(prop.name))
              return null

          }

          return {
            type: 'tableRow',
            children: [
              { type: 'tableCell', children: [{ type: 'strong', children: [{ type: 'text', value: propName }] }] },
              { type: 'tableCell', children: [{ type: 'inlineCode', value: prop.type.name }] },
              { type: 'tableCell', children: [{ type: 'inlineCode', value: prop.required ? 'Yes' : 'No' }] },
              { type: 'tableCell', children: [{ type: 'text', value: prop.description.replace(/@/g, '') || '-' }] },
            ],
          }
        })
        .filter(Boolean),
    ],
  }

  return table
}

Made with ❤️