使用 vitepress + pagedjs 在浏览器中实现书籍渲染的折腾过程(之解决一个奇怪的 BUG)

哈德韦

共 5771字,需浏览 12分钟

 · 2023-08-10

同名知乎专栏“哈德韦”(更好的阅读体验,点击阅读原文直达)




前情提要

在《【已被采纳】基本功不好,如何做开源贡献?使用 ChatGPT 写代码实录。》中提到,我使用 vitepress 打造了一个完美的写作平台。其中有一个不能嵌套包含的问题,也利用 ChatGPT 解决了,该贡献最终在 vitepress 1.0.0-beta.6 的发布中带上去了。

虽然可以在一屏中展示书籍的所有内容,但是展示的形式仍然不完美,即和真正的纸书呈现形式相差太大。调研了 pagedjs、pandoc、vitepress-export-pdf、https://github.com/vivliostyle/vivliostyle.js 等等,发现各自有各自的优势,结合自己的使用场景是在浏览器上拥有纸质书籍的阅读体验,同时希望拥有最少的代码侵入,最终决定还是使用 pagedjs 来在浏览器进行书籍页面渲染。

惊艳的效果

通过 pagedjs 的官网指引,在 vitepress 里引入了它,并加上了一些额外的 css 样式后,在开发模式下,完美渲染!

引入的方式是在 docs\.vitepress\config.ts文件中的 head 区增加如下内容:

export default withMermaid(    defineConfig({            head: [                [                    'script',                    {                        type: "text/javascript",                        async: "true",                        defer: "true",                        src: 'https://unpkg.com/pagedjs/dist/paged.polyfill.js'                    }                ],            ]    }))

然后,在 main.md 文件中增加相应的 style:

<style lang="css">
@page { size: A5 portrait; background-color: white;
@top-left { content: "数字身份认证技术与实践"; font-size: smaller; color: #979797; text-align: left; white-space: nowrap; }
@top-right { content: string(title); text-transform: uppercase; color: gold; white-space: nowrap; font-size: smaller; text-align: right; margin-right: 1em; }
@bottom-left { content: "从概念到实战的深入理解"; text-transform: capitalize; color: gold; white-space: nowrap; word-break: keep-all; font-size: smaller; }
@bottom-right-corner { content: counter(page) ' / ' counter(pages); white-space: nowrap; margin-left: -2em; }
@bottom-center { content: string(title); text-transform: uppercase; }}</style>

注意以上的 @page 指令,它将书籍页面左右展示,并在页眉和页脚中增加一个额外的文本,yarn docs:dev 执行后的效果如下:

奇怪的 BUG

然而,奇怪的是,一旦发布上线,以上效果全没了…… 只剩下了 pagedjs 将页面分割成多页展示,却没有页眉页脚了。

分析

看起来,vitepress 在本地开发模式下是启动了一个 Web 服务器来托管整个站点,但是一旦打包成静态页面文件,这个打包过程中会做一些事情,破坏了 pagedjs 的工作流。

说实话,我只想无脑使用这个工具,比如 vitepress 也好、pagedjs 也好,我能用就行,至于它们是怎么设计的,原理是什么,我没有兴趣知道。

然而现实是,我一用就发现不好用,有可能是姿势不对,但不去了解原理,就无法得知到底哪里不对,因为并没有现成的文档。vitepress 文档写得很好,然而没有也不应该专门为 pagedjs 的集成写相关的文档;对 pagedjs 来说也是如此,它已经有很完善的文档了,但却没有也不应该专门为在 vitepress 中的使用写相关的文档。

一般我是不建议阅读所谓框架或者库的源码的,毕竟只要会使用就可以了。但在使用过程中发现了问题,不得不去读一点源码。在开始阅读源码之前,我做了一些分析。

因为在开发者服务器运行时,一切正常。而打包完成后,不工作了。所以这很可能是由于打包过程中破坏(重建)了某些文件结构,导致最终 pagedjs 运行时找不到相关的文件

排查

顺着这个思路,我对比了一下 vitepress 开发者服务器运行时渲染的 html 和打包完成后的 html,发现在开发者服务器运行时下,html 中的 css 和 script 基本上和自己在 markdown 文件中引入的非常接近,而打包后则属于是面目全非,特别是 css 和 scripts 全部被重新命名并且以一种特别的方式引入。就连在 markdown 文件中内联的 css 样式,都被打包到独立文件中去了。

所以我怀疑 pagedjs 没有办法找到相关的样式文件,导致了最终渲染出来的内容缺失(通过 page 指令渲染的内容全没了)。

于是我去看了一下 pagedjs 的源码,了解到 preview 函数接收 3 个参数,其中第 2 个果然是接收一个 css 文件。


又通过其文档看到 css 规则的确是会被解析并最终应用在页眉页脚等地。


实验

之前使用的是引入 CDN 的 polyfill 文件,该文件自动完成所有的工作。现在,我需要使用调用 preview 函数的方式引入 pagedjs,从而为后面调参打好基础。于是删除 docs\.vitepress\config.ts 文件中的 head 部分。

export default withMermaid(    defineConfig({            head: [-                [-                    'script',-                    {-                        type: "text/javascript",-                        async: "true",-                        defer: "true",-                        src: 'https://unpkg.com/pagedjs/dist/paged.polyfill.js'-                    }-                ],            ]    }))

然后将 pagedjs 安装到 node_modules 里:

yarn add pagedjs

接着直接在 markdown 文件里添加

<script setup>import { onMounted } from 'vue'; import {Previewer} from "pagedjs"; 
onMounted(() => { setTimeout(()=>{ let paged = new Previewer(); let flow = paged.preview() .then((flow) => { console.log("Rendered", flow.total, "pages."); }) .catch((err) => { console.error('paged error'); console.error(err); }) ; }, 3000);});</script>

验证了在开发模式工作正常,在打包静态化后重现了之前的问题。

调参

以上实验证明了使用 Previewer 和直接从 CDN 引入 polyfill 是一样的。不过,Previewer 允许自定义参数,现在是默认的。

我将 markdown 内联的样式重新放在一个文件里,为了在编译打包后仍然能引用它,我将它放在了 public 目录下。


由于 css 文件是第二个参数,为了不改动第一个参数和第三个参数,我将它们置为 undefined

<script setup>import { onMounted } from 'vue'; import {Previewer} from "pagedjs"; 
onMounted(() => {setTimeout(()=>{ let paged = new Previewer();
- let flow = paged.preview()+ let flow = paged.preview(undefined, ['/pagedjs.css'], undefined) .then((flow) => { console.log("Rendered", flow.total, "pages."); }) .catch((err) => { console.error('paged error'); console.error(err); }) ; }, 3000);});</script>

问题解决

到此问题已经解决!完整源代码见: https://github.com/Jeff-Tian/AllAboutIdentity

在线体验链接: https://identity.jefftian.dev/main.html





如果有收获,请帮忙点赞点在看


领取微信备用金 领取哈德韦表情包





浏览 48
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报