vitepress-export-pdf
介绍
vitepress-export-pdf是什么?
vitepress-export-pdf
allows you to export your sites to a PDF file.
Installation
sh
npm install vitepress-export-pdf -D
then add script to your package.json
:
json
{
"scripts": {
"export-pdf": "press-export-pdf export [path/to/your/docs]"
}
}
Then run:
sh
npm run export-pdf
原理
研究原理之前先clone源码:
sh
git clone git@github.com:ZhongxuYang/vitepress-export-pdf.git
从Installation
中的package.json
可以知道,vitepress-export-pdf通过cli
的方式启用。
可以在package.json
中通过以下思路找到源代码:
package.json
json
"bin": {
"press-export-pdf": "bin/press-export-pdf.mjs"
}
bin/press-export-pdf.mjs
js
import '../dist/press-export-pdf.mjs'
package.json
json
"scripts": {
"build": "unbuild"
}
build.config.ts
js
entries: [
...fg.sync('src/commands/*.ts').map(i => i.slice(0, -3)),
]
src/commands/press-export-pdf.ts
ts
import { afterParse, beforeParse, runCli } from '../runner'
runCli(
'press-export-pdf',
// 在该方法里:调用registerCommands注册主函数
beforeParse,
// 在该方法里:检查cli入参,如不正确打印help log
afterParse,
)
src/runner.ts
ts
import type { CAC } from 'cac'
import cac from 'cac'
import pkg from '../package.json'
import { registerCommands } from './registerCommands'
type InterfaceParse = (cliInstance: CAC) => void
export const beforeParse = (cliInstance: CAC) => {
registerCommands(cliInstance)
// display cli version, display help message
cliInstance.version(pkg.version).help()
}
export const afterParse = (cliInstance: CAC) => {
if (!process.argv.slice(2).filter(Boolean).length)
cliInstance.outputHelp()
}
/**
* Parse CLI.
* @param programName - Name of program
* @param beforeParse - Function to run before parse
* @param afterParse - Function to run after parse
*/
export const runCli = (programName: string, beforeParse: InterfaceParse, afterParse: InterfaceParse) => {
try {
// create cac instance
const program = cac(programName)
beforeParse && beforeParse(program)
console.log(process.argv)
program.parse(process.argv)
afterParse && afterParse(program)
}
catch (error) {
process.exit(1)
}
}
src/registerCommands.ts
ts
export const registerCommands = (program: CAC) => {
// register `export` command
program
.command('export [sourceDir]', 'Export current VitePress site to a PDF file(default: docs)')
.allowUnknownOptions()
.option('-c, --config <config>', 'Set path to config file')
.option('--outFile <outFile>', 'Name of output file')
.option('--outDir <outDir>', 'Directory of output files')
.option('--debug', 'Enable debug mode')
.action(wrapCommand(serverApp))
// register `info` command
program
.command('info', 'Display environment information')
.action(wrapCommand(systemInfo))
}
上面的serverApp
就是主要函数了,我们来看一下它的实现:
ts
import { createServer as createDevApp } from 'vitepress'
import pkg from '../package.json'
export const serverApp = async (dir = 'docs', commandOptions) => {
// ...省略处理参数的代码
const {
sorter,
puppeteerLaunchOptions,
pdfOptions,
outFile = vitepressOutFile,
outDir = vitepressOutDir,
routePatterns,
enhanceApp,
} = userConfig
// 调用vitepress的createServer启动服务
const devServer = await createDevApp(sourceDir, {})
const port = Number(devServer.config.preview.host)
const servePort = Number.isFinite(port) ? port : 16762
const host = devServer.config.preview.host
const serveHost = typeof host === 'string' ? host : 'localhost'
const devApp = await devServer.listen(servePort)
devApp.printUrls()
logger.log('\n')
logger.tip('Start to generate current site to PDF ...\n')
try {
// 开始生成PDF
await generatePdf({
root: devApp.config.root,
port: servePort,
host: serveHost,
outFile,
outDir,
sorter,
puppeteerLaunchOptions,
pdfOptions,
routePatterns,
enhanceApp,
})
}
catch (error) {
logger.error(error)
}
// 首先关闭vitepress的服务
await devApp.close()
// 退出当前程序
process.exit(0)
}
下面是生成PDF的方法:
src/utils/generatePdf.ts
ts
export const generatePdf = async ({
root,
port,
host,
sorter,
outFile,
outDir,
puppeteerLaunchOptions,
pdfOptions,
routePatterns,
enhanceApp,
}: IGeneratePdfOptions) => {
// ...
// 记录所有的页面path
let exportPages = filterRoute(hashPages, routePatterns)
// 创建浏览器
const browser = await puppeteer.launch(puppeteerLaunchOptions)
// 循环访问所有页面
for (const { location, pagePath, url } of normalizePages) {
await browserPage.goto(
location,
{ waitUntil: 'networkidle2' },
)
// 生成pdf
await browserPage.pdf({
path: pagePath,
format: 'A4',
...pdfOptions,
})
const title = await browserPage.title()
browserPage.close()
}
singleBar.stop()
await browser.close()
await mergePDF(normalizePages, outFile, outDir)
fs.removeSync(tempPdfDir)
!fs.readdirSync(tempDir).length && fs.removeSync(tempDir)
}
释译
Dependencies
Dependencies | 作用 |
---|---|
enso | 用来直接运行ts文件,可以在dev环境不经过打包运行 |
cac | Command And Conquer是一个用于构建 CLI 应用程序的 JavaScript 库。 |
playwright | 模拟浏览器的渲染效果,抓取控制台信息等信息。支持Chorme,Firefox,Opera,Safari,IE |
picocolors | 打印带样式的log |
debug | 只有在携带指定参数时才打印对应的log,对于开发控制台工具类很有用 |
puppeteer | Puppeteer 是一个 Node 库,它提供了一个高级 API 来通过DevTools 协议控制 Chrome 或 Chromium 。在浏览器中可以做的大多数操作都可以使用Puppeteer完成 |
Code
process.exit()
在node程序中遇到process.exit时
- 如果传入1代表程序执行失败
- 如果传入0代表程序执行成功
process.argv
cli传入的参数。
- process.argv[0]: Node安装路径
- process.argv[1]: 当前Cli执行路径
- process.argv[2...]: 传入的参数
后言
其实我们可以发现,通过vitepress-export-pdf
导出的文件与通过浏览器的保存为PDF
文件本质上是一样的。使用vitepress-export-pdf
导出的唯一不同点在于,会把所有的页面集合到一个pdf文件中,而直接通过浏览器保存的pdf只有当前页面。😊