Vitepress 开发问题记录
给Vitepress快速添加全局搜索
一、用内置的local
搜索功能
在vitepress
的配置里加上这一段
{
"themeConfig": {
"search": {
"provider": "local",
"options": {
"translations": {
"button": { "buttonText": "搜索文档", "buttonAriaLabel": "搜索文档" },
"modal": {
"noResultsText": "无法找到相关结果",
"resetButtonTitle": "清除查询条件",
"footer": {
"selectText": "选择",
"navigateText": "切换",
"closeText": "关闭"
}
}
}
}
}
}
}
随后出现的效果如图:
二、使用社区的插件vitepress-plugin-search
或者vitepress-plugin-pagefind
以vitepress-plugin-search
为例
安装依赖
pnpm add -D vitepress-plugin-search flexsearch
vite
配置中添加插件
import { SearchPlugin } from 'vitepress-plugin-search'
const searchOptions = {
previewLength: 62,
buttonLabel: 'Search',
placeholder: 'Search docs',
}
export default {
vite: {
plugins: [
SearchPlugin(searchOptions),
],
},
}
效果图:
给Vp页面加上访问量记录
方案一、使用busuanzi
服务
实现过程
- 安装
busuanzi.pure.js
pnpm add -D busuanzi.pure.js
- 在页面路由变化的时候,调用
busuanzi
服务
export default {
enhanceApp: ({ router }: EnhanceAppContext) => {
router.onAfterRouteChanged = () => {
isClient && busuanzi.fetch()
}
},
}
通过md编译插件为每个页面添加上阅读量组件
添加
busuanzi_value_page_pv
唯一标识至标签中
阅读量:<span id="busuanzi_container_page_pv"><span id="busuanzi_value_page_pv" /></span>
实现原理:
当页面路由变化后,在变化后的页面发送busuanzi
的请求,并通过jsonp回调函数执行,添加访问量到ID
中,完成访问量的记录。
常见问题
busuanzi.fetch()
调用前,需要判断是否是客户端,否则Node
打包会报错。referrer
默认是strict-origin-when-cross-origin
,需更改referrer
策略,以达到发送网站信息,准确记录网站访问量
- 比如:
unsafe-url
、no-referrer-when-downgrade(推荐)
注意:
strict-origin-when-cross-origin
是一个CSP(Content Security Policy)
的策略指令,用于指定浏览器在跨域请求时如何发送Referer
头信息。当浏览器从一个站点跳转到另一个站点时,Referer
头信息会告诉目标站点请求来自哪个站点。这个指令的作用是在跨域请求时,只有在目标站点和源站点的协议、主机名和端口号都相同时,会发送Referer
头信息,否则不发送。
例如,如果当前页面的URL
是 https://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
插件实现
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
}