← 全部文章

技术笔记 · 2026-09-14

主包能运行,附件预览为什么仍在旧 WebView 中失败

聚焦第三方依赖的兼容边界:区分语法、运行时 API 与 Worker 加载,建立可复现的浏览器验证链,而不是仅修改编译 target。

  • WebView
  • 浏览器兼容
  • PDF.js
  • Worker

关联案例:智能台账与订单管理。预览功能涉及 PDF.js、SheetJS 和 docx-preview;本文只讨论“同一产物在不同内核上能否运行”,不展开文件解析和分页实现。

首页正常,并不能证明预览依赖兼容

附件库通常按需加载。打开首页时没有执行的依赖,会在用户点击 PDF 或其他文档时才进入运行环境。因此,“网站正常打开”与“预览模块正常执行”是两个不同的验证点。

桌面宿主内嵌的 WebView 也不一定等于电脑上刚更新的浏览器。调试应记录宿主版本、实际内核、平台、构建模式与资源版本,确保比较的是同一组产物。

先判断在哪一层失败

层级 典型现象 首先核对
语法解析 模块加载即报 SyntaxError 出错资源与具体语法、依赖构建产物
运行时 API 某函数不存在 内核支持、polyfill 范围和调用位置
Worker 启动 主模块已加载,但解析任务失败 Worker 地址、状态码、版本与运行环境
渲染调用 页面对象已拿到,绘制阶段失败 Canvas、尺寸和传入的渲染参数

不要用“白屏”统称这些问题。它只能描述用户看到的结果,无法说明修复应发生在打包、资源加载还是渲染层。

target 不能替你补齐所有浏览器能力

降低编译目标可以转换一部分现代语法,但不会凭空实现缺失的 DOM 方法,也不会保证所有第三方资源和 Worker 都经过同一条转换链。

用尽量保守的语法做能力检查,有助于区分入口是否已经运行。下面只是排查探针,不代表这些能力存在就一定支持完整预览:

var capabilities = {
  promise: typeof Promise === 'function',
  fetch: typeof fetch === 'function',
  worker: typeof Worker === 'function',
  abortController: typeof AbortController === 'function',
  replaceChildren:
    typeof Element !== 'undefined' &&
    typeof Element.prototype.replaceChildren === 'function'
}
console.table(capabilities)

如果脚本本身在解析阶段就失败,能力探针也未必执行得到;此时要看控制台中的出错文件与资源响应,而不是继续往失败模块内部加日志。

Worker 是另一份需要验证的产物

主线程入口可以加载,不代表 Worker 地址正确或其脚本兼容。对照 Network 检查 Worker 是否真的返回脚本,是否被路由回退成 HTML,以及是否与主库来自配套版本。

项目使用单独的 PDF Worker 资源入口。具体文件路径属于依赖版本的一部分,升级时应重新核对,不能把某个版本的目录结构当成永久 API。

PDF.js 的公开示例展示了“加载文档、取页、渲染”的分层流程,也提醒不要同时在同一 Canvas 上绘制两页,见 PDF.js Examples。这能帮助把失败定位到更具体的阶段。

兼容处理应该有明确退出条件

可以选择让宿主升级内核、使用依赖提供的兼容构建、补齐必要 API,或为不支持的环境提供下载入口。选择哪条路径,需要对照真实环境和维护成本。

如果历史修复涉及固定旧版本,应记录它解决了哪个兼容问题、受影响的环境和后续升级条件,并评估该版本的维护与安全状态。仅仅在当前测试机上可用,不足以成为长期停留旧版的理由。

验证同一份生产产物

以一份小型已知文件为起点,在目标宿主与现代浏览器中打开同一份构建产物,逐步检查入口、依赖、Worker 和渲染阶段。再覆盖文档切换与失败反馈。

开发服务器会改变资源路径和加载方式,所以最终验证必须包含构建产物。H5 浏览器验证也不能代替微信小程序或桌面宿主内的真实 WebView 验证。

这类问题的关键,是把“运行环境”也纳入依赖契约,而不是把所有失败都归因于业务代码。