使用pkg打包puppeteer项目提示Passed function is not well-serializable报错的原因分析及解决方法
最近使用node写了一个使用Puppet的自动化程序,为了方便分发给未安装node环境的用户,准备使用pkg对项目进行打包。打包过程中遇到了 Error: Passed function is not well-serializable!的报错,在此记录下问题的解决方法。
原因分析
Passed function is not well-serializable! 这个报错既不是浏览器抛的,也不是 pkg 抛的,而是 Puppeteer 自己抛的,而这个问题恰好是下面两个原因叠加造成的。
Puppeteer传递回调函数的原理
page.evaluate(fn, ...args) 里的函数是在 Chromium 渲染进程(页面上下文) 里执行的,而JS代码跑在Node 进程里。两个进程之间只能通过 CDP(Chrome DevTools Protocol)传字符串,不能直接传函数对象。所以在 Puppeteer 的 ExecutionContext.js 里,序列化的核心就是下面这两步:
1 | let functionText = pageFunction.toString(); // 1. 靠 toString 拿源码 |
这个功能完全依赖 fn.toString() 返回一段合法的 JS 源码。直接用 node 跑时没问题,因为源码就在磁盘上,.toString() 能还原出函数体。
Pkg打包的过程
pkg官方文档记录了以下内容:
Files specified as
scriptswill be compiled usingv8::ScriptCompilerand placed into executable without sources.By default, your source code is precompiled to v8 bytecode before being written to the output file.
这说明pkg 打包的过程是把项目里的 JS 文件,用 v8::ScriptCompiler 全部编译成V8 字节码,塞进一个snapshot虚拟文件系统里,原始源码并没有不写进 exe。源码被剥离之后,运行时你的函数是从字节码”重建”出来的。这时候再调用 fn.toString(),拿到的就不是原始代码了,而是一个类似function () { [bytecode] } 或者 function () { [native code] }这样的占位符,它显然没法在 new Function(...) 里被重新解析成合法函数。于是 Puppeteer 在序列化 page.evaluate(dados => {...}) 时会依次产生下面的后果:
.toString()拿到的是占位符;new Function(...)校验失败;- 抛出
Passed function is not well-serializable!。
解决方法
添加 --public 参数
一个最简单的方法就是在使用pkg打包时加入 --public 参数,或根据实际选择合适的参数
| 参数 | 作用 |
|---|---|
--no-bytecode |
完全关闭字节码,所有文件(含 node_modules)都按明文 JS 打包 |
--public |
只把顶层项目源码按明文打包(node_modules 仍字节码) |
--public-packages "pkgA" |
强制指定 npm 包按明文打包 |
默认(--private 行为) |
全部字节码,源码隐藏 |
其他方法
- 改用
eval()包一层:把代码写进字符串里用eval拼接,避免让 Puppeteer 对函数做.toString()。 - 直接给
page.evaluate传字符串:Puppeteer 的evaluate也接受字符串参数,字符串不经过函数序列化。
参考链接
- Stack Overflow 原问题:Passed function is not well-serializable
- Puppeteer
ExecutionContext.js(含 “not well-serializable” 校验逻辑) - Puppeteer
helper.js(evaluationString字符串序列化) - pkg README(
--public/--no-bytecode/--public-packages说明)