之前都是使用react-pdf来渲染pdf文件,这次有个需求是要兼容xp环境,xp上chrome最高支持到49,虽然说iframe或者embed都可以实现预览pdf,但为了后续的定制化需求,还是需要使用js库来渲染。
chrome 49测试环境
能用的测试环境是关键,这里使用chrome 49,是为了兼容xp。
我使用vmware安装了windows10虚拟机,再安装chrome 49来模拟。

pdf.js
一开始我觉得既然react-pdf不能用,那我们就找它封装的原始库pdf.js。
首先参考利用pdf.js在线展示PDF文档 - 老码识途呀 - 博客园,找到能用的pdf.js版本(pdfjs-2.5.207-es5-dist),重点是要支持es5。
使用umi搭建测试Demo
3.1. umi4
第一次我用的umi4搭建的,主要代码如下:
json
//package.json
"dependencies": {
"pdfjs-dist": "2.5.207",
"umi": "^4.4.11"
},
typescript
//index.tsx
import { useEffect, useRef } from "react";
import * as pdfjs from 'pdfjs-dist';
//重点是要用es5
import pdfjsWorker from 'pdfjs-dist/es5/build/pdf.worker.entry.js';
pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker;
const pdfUrl = 'http://xxx.xxx.xxx.xxx/xxx.xxx.xxx.xxx.pdf';
const PdfTest = () => {
const pdfContainer = useRef();
useEffect(() => {
// Loading a document.
var loadingTask = pdfjs.getDocument(pdfUrl);
loadingTask.promise
.then(function (pdfDocument) {
// Request a first page
return pdfDocument.getPage(1).then(function (pdfPage) {
// Display page on the existing canvas with 100% scale.
var viewport = pdfPage.getViewport({ scale: 1.0 });
var canvas = document.getElementById("theCanvas");
canvas.width = viewport.width;
canvas.height = viewport.height;
var ctx = canvas.getContext("2d");
var renderTask = pdfPage.render({
canvasContext: ctx,
viewport: viewport,
});
return renderTask.promise;
});
})
.catch(function (reason) {
console.error("Error: " + reason);
});
}, [])
return (<canvas
id="theCanvas"
ref={pdfContainer}
/>);
}
export default PdfTest;
另外umi4打包时还支持添加兼容性配置:
typescript
//.umirc.ts
export default defineConfig({
targets: {
chrome: 49,
},
legacy: {},
});
实测是可以访问的。

3.2. umi2 + react-pdf
由于项目比较老,还在用umi2,不支持legacy配置,光使用umi4是不够的。
另外既然pdf.js可以,理论上react-pdf也是支持的,只要找到对应的版本号。我找到的是[email protected]。
javascript
//reactPdf.js
import React, { useState } from 'react';
import { Document, Page, pdfjs } from 'react-pdf';
import pdfjsWorker from 'pdfjs-dist/es5/build/pdf.worker.entry.js';
pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker;
const pdfUrl = 'http://xxx.xxx.xxx.xxx/xxx.xxx.xxx.xxx.pdf';
function ReactPdf() {
const [numPages, setNumPages] = useState(null);
const [pageNumber, setPageNumber] = useState(1);
function onDocumentLoadSuccess({ numPages }) {
setNumPages(numPages);
}
return (
<div>
<Document file={pdfUrl} onLoadSuccess={onDocumentLoadSuccess}>
<Page pageNumber={pageNumber} />
</Document>
<p>
Page {pageNumber} of {numPages}
</p>
</div>
);
}
export default ReactPdf;
javascript
//config.js
export default {
targets: { //配置浏览器最低版本,比如兼容ie11
chrome: 49, ie: 9
},
}
然而实测报错:

错误排查
这里有一个很重要的点,那就是如何找到出错的代码?
umi2是支持不压缩代码的:

所以我修改了打包命令:
json
//package.json
"build": "cross-env COMPRESS=none umi build",
重新发布后可以看到出错的地方:

定位到源码:

这里可以明显看到出错的原因是不支持async,也就是es6的功能。
报错的这段代码其实出自pdf.js:
javascript
//[email protected]/src/display_utils.js
async fetch({ name }) {
if (!this.baseUrl) {
throw new Error(
'The CMap "baseUrl" parameter must be specified, ensure that ' +
'the "cMapUrl" and "cMapPacked" API parameters are provided.'
);
}
if (!name) {
throw new Error("CMap name must be specified.");
}
所以我们的目标是打包时把pdf.js的源码转换为es5。
我试了很多方案,比如修改react-pdf源码,把所有pdfjs-dist的引入改成es5:
javascript
//[email protected]/src/Document.jsx
// import * as pdfjs from 'pdfjs-dist';
import * as pdfjs from 'pdfjs-dist/es5/build/pdf';
改完以后重新打包并替换node_modules下的文件夹:

实测仍然报错:

另外在本地开发时定位到:

打断点自动跳转到源码:

说明就算我把react-pdf中的所有引入都改成了es5,pdfjs-dist还是会有一些工具类会使用es6的方法。
实际上我们需要的是把pdfjs-dist转换为es5。
我试了很多,比如配置@babel/preset-env、@babel/plugin-transform-runtime等,都没用。
我认为umi默认不会对node_modules下的文件做转换,因此需要把pdfjs-dist加入到umi自身的babel转换中:

javascript
//config.js
extraBabelIncludes: [
/[\\/]pdfjs-dist[\\/]/, // 匹配 node_modules/pdfjs-dist
]
此时打包后,可以看到async已经被转换了:

在chrome 49上也能正常访问了:

至此我们完美兼容了老版本浏览器,在此记录下,主要是排查过程。