1 Star 0 Fork 11

coder_lw / wiki

forked from deepinwiki / wiki 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
vuepress.md 19.01 KB
一键复制 编辑 原始数据 按行查看 历史
htqx 提交于 2023-08-02 00:13 . public 资源

vuepress

前言

vuepress 是一个 markdown 转网站工具。

vue 是一个前端开发的框架,vuepress 是基于 vue 的将 markdown 文档转换为网站的框架。它的特点就是几乎不需要过多的配置,即可以将指定目录下的 md 文件,转换为对应的网站。

这对于使用 markdown,又想可以进行网页浏览的创作者来说是一个不错的工具。

安装

# 2.0 测试版支持 vite
# vite 是一个开发时的网站服务器,让用户可以方便预览输出结果
# 你可以边写 md 文档,实时查看输出的网页
yarn add -D vuepress@next

package.json 样例:

{
  "devDependencies": {
    "vuepress": "^2.0.0-beta.64"
  },
  "scripts": {
    "dev": "vuepress dev blogs",
    "build": "vuepress build blogs"
  }
}
# 上述配置定义了两个命令,其中 blogs 是存放 md 文档的目录

# 开启网站预览服务器
yarn dev
# 构建输出最终网站
yarn build

只要这样配置,既可以得到对应的网站。其中,.md 变为 .html 文件,readme.md 或者 index.md 会成为 index.html 即默认的首页。

但是并没有导航菜单。可以手动输入网址来查看最终效果。

架构

vuepress node app -> { 临时模板文件 + vue app client } --(bundler node app)--> dev server 或者 ssr pre-render(网页预渲染)。

首先是 vuepress 通过 node 运行,输出临时文件(模板、数据、vue app),然后通过打包器,也就是 vite 启动预览服务器或输出最终网站。

每一个 md 文档都会转化为 vue sfc(单文件组件),可以在 md 文档中使用相关的标签。(当然这种侵入式的做法不太让我满意,我希望的是 md 是文档,至于怎么变成 html,这是另一种需求,应该独立配置)

名词解释:ssr 网页预渲染技术,是将很多 js 的结果作为输出,从而让网页尽可能的静态化,而无法静态化的部分,就留着作为网页中的 vue app js 代码执行。

  1. vuepress app:构建内容的构建器
  2. vue app:在最终网页中执行的 js 部分,也就是所谓的客户端

这两个都是可以配置的。

参考:https://v2.vuepress.vuejs.org/zh/advanced/architecture.html

配置

目录:

  1. 项目目录:vuepress.config.js /.ts / .mjs
  2. md 文档目录:.vuepress/config.js
  3. 客户端配置文件: (同目录)client.js
// config.js
// 用户配置
import {defineUserConfig } from 'vuepress'

export default defineUserConfig({
	lang: 'zh-CN', // 设置页面的默认语言
	title: '网站标题', // 作为页面标题的后缀: 网页标题|网站标题
	description: '默认网页简介', // <meta name="description" />
	base: '/', // 根目录,如网站位于 xxx 子目录下:/xxx/
	// 在 head 标签内插入默认的元素 <link rel="icon" href="/logo.png" />
	head: [['link', { rel:'icon', href:'/logo.png' }]],  
	// 本地化,可以设置 lang、title、description、head
	locales: { '/en-US/':{lang: 'en-US'}}, 
})

// 主题配置
import {defaultTheme}from 'vuepress'

export default {
	// 使用官方默认的主题
	theme: defaultTheme({
		// 导航栏
		navbar: [{
			text: '首页',
			link: '/',
		}],
	}),
}

// 杂项
export default {
	dest:`${sourceDir}/.vuepress/dist`, // 网站的输出目录
	temp:`${sourceDir}/.vuepress/.temp`, // 临时文件目录
	cache:`${sourceDir}/.vuepress/.cache`, // 缓存目录
	public:`${sourceDir}/.vuepress/public`, // 资源文件目录(公共的图片等)
	debug:false, // 是否启动调试模式
	permalinkPattern: ':year/:slug.html'// 永久链接模板,例:2023/xxx.html
}

// 插件配置
import myPlugin from './my-plugin.js'

export default {
	plugins: [ myPlugin() ],
}

// 配置打包工具
import {viteBundler} from 'vuepress'

export default {
	bundler:viteBundler({}),
}

注意: js 要求每个文件只能有一个 export default,所以实际要配置的内容是要合并在一起的。

// client.js
// 客户端配置
import {defineClientConfig} from '@vuepress/client'
import MyComponent from '第三方组件.vue'

export default defineClientConfig({
	// app: vue app
	// router: vue router,一个虚拟网页链接的工具(所谓路由)
	// siteData: base,lang,title,description,head,locales 配置对象
	enhance({app, router, siteData}){
		app.component('第三方组件’, MyComponent)
	},
	// vue 客户端 app 会调用 steup 组合式 api
	setup(){},
	// 全局组件列表
	rootComponents:[],
	// 布局
	layouts:{},
})

我的案例

这个工具默认就能产生一个真实的网站,这点让我很欣慰,因为很多同类产品,都要学习一大堆配置内容,才能最终出网站。

但是默认的网站有个最大的缺陷,那就是没有导航,不知道为啥没有提供这个。当然有导航栏插件,但是我们希望可以动态的生成一个目录,可以导航到所有文章的页面(这种功能也叫网站地图)。

而现实是只有侧边栏生成了当前页面的目录,而不是我们希望的整个网站的目录。

另外一个就是缺少搜索功能。搜索还是挺有用的,当然一个比较取巧的方案是调用百度或者谷歌,使用关键字 "site:我们的网站" 来搜索网站内容。但是有自己独立的搜索会显得网站比较精致和专业。

目标:

  1. 网站地图
  2. 本地搜索
# 本地搜索要另外安装插件
# 安装插件要注意版本号,找到适合的版本号
# 有问题就删掉 yarn remove @vuepress/plugin-search
yarn add -D @vuepress/plugin-search@next

yarn 是靠 package.json 定义组件之间的依赖关系的,安装后我的内容是:

{
  "devDependencies": {
    "@vuepress/plugin-search": "^2.0.0-beta.66",
    "vuepress": "^2.0.0-beta.66"
  },
  "scripts": {
    "dev": "vuepress dev blogs",
    "build": "vuepress build blogs"
  },
  "name": "deepinwiki",
  "version": "1.0.0",
  "description": "deepin wiki,爱好者的共享社区内容",
  "main": "index.js",
  "author": "htqx",
  "license": "MIT"
}

vite 项目中的资源(也就是图片等),只要在 md 文档中引用的,会自动被打包到最终网站构建目录中去。但是,如果你想复制一些独立的资源,那么你得将它放在 blogs/.vuepress/public 目录中。其中 blogs 是我 md 文档的存放目录。.vuepress 是配置的主目录。存放在 public 的任何文件,都会原封不动拷贝到最终输出。

资源的路径以 public 为根目录,如 public/dddu32.ico,在 js 代码中就是 dddu32.ico 即可。

这里我提前准备好三个不同大小的 ico 文件,分别是 dddu32.ico、dddu48.ico、dddu96.ico 存放在 public 目录中。

目录结构:

  1. 项目根目录
    1. node_modules:依赖的组件(yarn 管理)
    2. package.json:项目的配置(yarn 更新,也可以自己填写相关信息)
    3. blogs:文档子目录
      1. .vuepress
        1. config.js:主配置
        2. client.js:客户端配置
        3. public:独立的资源
        4. .temp:临时文件
        5. .cache:缓存
        6. dist:最终网站生成的目录(yarn build,然后发布这个目录到网站上即可)
// blogs/.vuepress/config.js

import { createPage, defineUserConfig, viteBundler } from 'vuepress'
import { defaultTheme } from 'vuepress'
import { getDirname, path } from '@vuepress/utils'
import { searchPlugin } from '@vuepress/plugin-search'

const dir_name = getDirname(import.meta.url)
const logo_url = path.resolve(dir_name, '../pics/dddu.jpg')

export default defineUserConfig({
	lang: 'zh-Hans', // 设置页面的默认语言
	title: 'deepin天天用', // 作为页面标题的后缀: 网页标题|网站标题
	// <meta name="description" />
	description: 'deepin天天用QQ群:7343311', 
	base: '/', // 根目录,如网站位于 xxx 子目录下:/xxx/
	// 插入 head 标签下,一般是网站的元信息
	// 这里指定网站的小图标 
	// <link rel="icon" href="dddu32.ico" sizes="32x32" />  
	head:[['link',{rel:'icon',href:"dddu32.ico", sizes:"32x32"}],
		['link',{rel:'icon',href:"dddu48.ico", sizes:"48x48"}],
		['link',{rel:'icon',href:"dddu96.ico", sizes:"96x96"}],],
	// 本地化,可以设置 lang、title、description、head
	locales: {
		'/': { lang: 'zh-Hans' },
		'/en-US/': { lang: 'en' },
	}, 
	// 主题
	theme: defaultTheme({
		// 本地化按钮
		locales: {
			'/': { selectLanguageName: '中文' },
			'/en-US/': { selectLanguageName: 'English',
			navbar:["../vuepress.html"] },
		},
		// 导航栏
		// 将 gitee.com 替换为真实的网站即可使用搜索引擎来搜索内容
		navbar: ['./vuepress.html', 
		{text:"全站搜索", 
			children:[{text: '百度',link: 'https://www.baidu.com/s?wd=site:gitee.com deepinwiki'},
			{text: 'google', link: 'https://www.google.com/search?q=site:gitee.com deepinwiki'}]
		}],
		// git 仓库
		repo: 'https://gitee.com/deepinwiki/wiki.git',
		// 网站标志,左上角的图标
		logo: '@fs/' + logo_url,
		// 侧边栏目录深度
		sidebarDepth: 4,

	}),
	plugins:[
		// 本地搜索插件
		searchPlugin({
			// 搜索结果最大个数
			maxSuggestions:10,
		}),
	],
	// 初始化钩子,这里编程创建网站地图
	async onInitialized(app){
		// 找首页
		let root = app.pages.find(elem=> elem.path === '/')
		// 准备将目录内容插入到首页内容后面
		let append = '\n\n## 目录'
		app.pages.sort((a,b)=>b.data.git.updatedTime - a.data.git.updatedTime)

		app.pages.forEach((element,index) => {
			let slug = element.slug.trim()
			if (!slug) slug = "404"
			// [slug](link) 是 md 添加链接的语法
			// 为了节省空间,一行展示 4 个文件
			if ((index) % 4 == 0) append += '\n\n1. '
			append += `[${slug}](${element.path}) &ensp;&ensp;`
		})
		// 把原来的首页移除
		app.pages.splice(app.pages.indexOf(root), 1);
		// 把新的首页替换进去
		let new_root = await createPage(app, {
			path: '/',
			content: root.content + append,
		})
		app.pages.push(new_root)

		// 设置英语首页
		let en_root = app.pages.find(elem=>elem.path === '/README.en.html')
		en_root.lang='en'
		en_root.path='/en-US/'
	},
})

以上就是全部配置了。这里并不需要使用到 client.js 配置客户端,客户端主要是改变主题的布局,我使用了官方的方案。

这里添加了导航栏:本文的链接,语言选择按钮,gitee 仓库链接,还有搜索链接(只会搜索标题,但也不错了)。

然后是动态生成一个首页,它是根据默认首页的内容,然后替换添加目录。目录是通过枚举所有页面,找到他们的链接。很简陋的方案,没有任何美化。

为了演示,提供了英语的本地化目录,但只有一个首页。

这里有一个技术要点,如果资源不存在 public,而是相对 blogs 目录存放的一些资源,要访问它需要使用 '@fs/绝对路径' 定位。

但这样只能在调试的时候临时访问,而不会复制到最终构建的网站中,所以最好不要这么做。

独立的资源当然最好还是放在 public 目录。

// 在 config.js 编程实现的方法
import fs from 'fs'
// 直接拷贝到项目的 public 资源目录(或其他可用的目录)
// 然后在 html 直接用相对目录 ./dddu.jpg 使用即可
fs.copyFileSync('blogs/pics/dddu.jpg', 'blogs/.vuepress/public/dddu.jpg')

编程

包结构

  1. bundler-vite: 使用 vite 打包和启动预览服务器
  2. cli:命令行模块
  3. client:客户端
  4. core:构建内容输出的核心库
  5. markdown:使用 markdown-it 解析 md 文档
  6. shared:公共工具函数
  7. utils:只在 node 中使用的工具函数
  8. ecosystem
    1. plugin-${name}:官方插件
    2. theme-default:默认主题
    3. vuepress:vuepress-vite 的封装,包含
      1. cli
      2. bundler-vite
      3. theme-dafault

内置组件

  1. ClientOnly
  2. Content

组合式 api 中的 steup() 中不能使用浏览器的 api,因为 node.js 没有这些 api,只有当 onBeforeMount() 或 onMounted() 生命周期后,才能使用 DOM api。也就是它无法 SSR 服务器预渲染,因此使用 ClientOnly 组件包裹这些代码留到客户端 vue app 运行的代码。

Content 组件用于开发阶段,预览 md 文档内容。

vuepress node app

前面说过,这个 node app 是负责构建内容输出的,它有一些实用的 api 这里摘录一下。这套 api 由 @vuepress/core 包提供。

  1. App.dir:获取各种目录
  2. App.witeTemp:写入临时目录,客户端使用 "@temp/文件名" 获取
  3. Page.crteaPage(): 动态创建页面,也就是动态构建一个 md 文档
    1. key: 用于文件名
    2. path: 网站路径
    3. title:标题
    4. lang:语言
    5. frontmatter: md 文档开始处的 yaml 段
    6. headers:内容标题
    7. date:创建时间
    8. deps:关联的代码段源文件
    9. links:链接集合
    10. data:元数据
    11. content: 原始 md
    12. contentReandered: 渲染结果 html
    13. sfcBlocks: sfc 信息块
    14. routeMeta: 路由元信息
    15. slug:模式匹配后的文件名
    16. filePath:md 文件路径
import {createPage} from '@vuepress/core'

export default {
	// Hook:初始化后
	// 初始化后可以使用 app.pages,也就是页面对象可用
	async onInitialized(app) {
		app.pages.push(
			await createPage(app, {
				path: '/foo.html',
				frontmatter:{
					layout: 'Layout',
				},
				content: `这是动态生成的 md 文档:${{ new Date() }}`,
			})
		)
	}
}

Frontmatter 前导段

可以在 md 文档开始处,记录一些元信息用于转换。它的格式是 --- 开始和结尾的,内部是 yaml 格式。

yaml 格式参考:https://www.runoob.com/w3cnote/yaml-intro.html

它的很多项其实都是在替换默认的站点信息。

---
# 这是 yaml 的注释
# 定义都是按照如下格式
# : 值
# ? key
# key: 值
# 一般 key 不用定义,直接用字符串
# : 后注意要带空格

date: 2023-07-12 # 日期 yyyy-mm-dd
description: '网页描述'
# 或者
description: 网页描述第一行
网页描述第二行
# yaml 使用相同空格的缩进来表示同一层级的内容
# 或对象压缩为 {date:xxx,description:xxx}
# 数组压缩为 [1,2,3,4]
# 非压缩使用 - 前导的同缩进为兄弟元素
# 用空行区分不同的对象

# 在 head 标签中插入标签
# <meta name="foo" content="yaml 的缩进有点眼花" />
head:
	- - meta
	  - name: foo
	    content: yaml 的缩进有点眼花
lang: 'zh-CN'
loyout: '布局由主题提供'
permalinkPattern: ':year/:month/:day/:slug.html'
routeMeta: {'key', 'haha'} # 路由使用的元信息
title: '网页标题'
---

client api

由 @vuepress/client 包提供客户端可用的 api。

组合式 api(composition api):

  1. usePageData :当前页面的 Ref 对象
  2. usePageFrontmatter:前导段对象
  3. usePageHead:Head 中的 Ref 对象(包括前导段)
  4. usePageHeadTitle:标题的 Ref 对象
  5. usePageLang: 语言的 Ref 对象
  6. useRouteLocale: 本地化
  7. useSiteData:站点数据
  8. useSiteLocaleData:本地化中的站点数据
  9. defineClientConfig():客户端配置
  10. withBase(): 获取在网站中的绝对地址
  11. __VUEPRESS_SSR__: 判断是否在 ssr 中的环境变量

插件 api

由 @vuepress/core 包提供。插件的生命周期是很重要的。

调用时立即处理:

  1. name:插件名称
  2. multiple:是否可以存在多个实例

初始化 app 时处理:

  1. extendsMarkdownOptions:markdown 配置
  2. extendsMarkdown:md 插件实例
  3. extendsPageOptions:页面配置
  4. extendsPage:页面实例
  5. onInitialized():初始化

准备文件时处理:

  1. clientConfigFile:客户端配置文件的路径
  2. onPrepared():准备文件后

在 dev / build 时处理:

  1. extendsBundlerOptions:打包器配置
  2. alias:别名
  3. define:全局常量
  4. onWatched():监听 md 变化并自动更新浏览器
  5. onGenerated():完成 html 输出

核心流程与 Hooks(钩子、生命周期的回调函数)

  1. 加载用户配置(load user config)
  2. 创建 vuepress app
  3. 加载主题和插件
  4. extendsMarkdownOptions & extendsMarkdown
  5. 加载页面 & extensPageOptions & extendsPage
  6. onInitialized()
  7. 预处理临时文件 & clientConfigFile
  8. onPrepared()
  9. 解析打包器配置 & extendsBundlerOptions & alias & define
  10. 启动开发用网页服务器(支持动态更新)& onWatched()
  11. 或生成静态网页文件 & onGenerated()

主题 api

主题也是插件。

  1. name:命名约定:@org/vuepress-theme-foo
  2. multiple: false
  3. extends:继承父主题
  4. plugins:主题使用的插件
  5. templateBuild:html 模板
  6. templateDev:开发时的 html 模板

默认的 html 模板:@vuepress/client/templates/build.html

配置主题:

  1. locales: 主题本地化
  2. colorMode: auto | light | dark 配色方案
  3. colorModeSwith: 是否显示配色切换按钮
  4. home:首页链接,'/'
  5. navbar:导航栏。[{text, link} | {text, array} | link]
  6. logo: 图标,资源要放在 .vuepress/public 目录下
  7. repo: git 仓库地址
  8. sidebar: 侧边栏导航 [{text,link,children} | path:{} ]
  9. sidebarDepth: 默认生成文档的 2 级以上标题
  10. 自定义容器
    1. tip
    2. warning
    3. danger
    4. notFound
    5. backToHome
  11. themePlugins: 主题插件

自定义主题的插槽

主题提供了插槽,使用布局来变更插槽原本的内容。

  1. navbar:导航栏
  2. navbar-before
  3. navbar-after
  4. sidebar:侧边栏
  5. sidebar-top
  6. sidebar-bottom
  7. page:页面
  8. page-top
  9. page-bottom
  10. page-content-top:正文前
  11. page-content-bottom:正文后
import {defineClientConfig} from '@vuepress/client'
import Layout from './layouts/Layout.vue'

export default defineClientConfig({
	layouts:{
		Layout,
	}
})
<!-- ./layouts/Layout.vue -->
<script setup>
import ParentLayout from '@vuepress/theme-default/layouts/Layout.vue'
</script>
<template>
	<ParentLayout #sidebar-top>
		<div>自定义侧边栏</div>
	</ParentLayout>
</template>

组件替换

官方主题在用户配置中,使用了别名 @theme/xxx.vue 定义了相关组件。只要替换掉别名的组件,即可完成组件的替换。

import { getDirname, path } from '@vuepress/utils'
import { defaultTheme, defineUserConfig } from 'vuepress'

const __dirname = getDirname(import.meta.url)

export default defineUserConfig({
  theme: defaultTheme(),
  alias: {
    '@theme/HomeFooter.vue': path.resolve(__dirname, './components/MyHomeFooter.vue'),
  },
})

插件

官方组件的命名习惯,如 back-to-top 实际对应 @vuepress/plugin-back-to-top 插件。

  1. back-to-top: 返回顶部
  2. container:自定义容器
  3. external-link-icon:外部链接提示图标
  4. medium-zoom:图片缩放
  5. nprogress:进度条
  6. register-components:自动注册组件
  7. docsearch:文档搜索,需要安装配置和在 algolia 网站注册帐号
  8. search:本地搜索,代价是增大文件
    1. maxSuggestions: 结果显示多少条
  9. active-header-links:网页链接自动跟踪当前的标题,如 xxx.html#插件
  10. git:收集 git 信息,如创建时间、更新时间、贡献者
  11. themeData:主题数据
  12. toc:创建目录

参考

  1. 官方教程:https://v2.vuepress.vuejs.org/zh/advanced/cookbook/
1
https://gitee.com/coder_lw/wiki.git
git@gitee.com:coder_lw/wiki.git
coder_lw
wiki
wiki
master

搜索帮助