- AI 应用
- 机器学习
- 模型优化
【免费下载链接】coremltools
Core ML tools contain supporting tools for Core ML model conversion, editing, and validation.
导读
本文以 coremltools 仓库内嵌的 protobuf 源码(deps/protobuf/js/README.md)为主体,完整讲解 JavaScript 版 Protocol Buffers(官方 npm 包名为google-protobuf)的运行时结构、环境搭建、protoc --js_out代码生成参数,以及 CommonJS 与 Closure 两种导入风格下的实际用法。读完本文,你将掌握:如何在本仓库内跑通 JS 运行时的自测流程(npm install && npm test),如何用protoc把.proto文件编译成可直接在浏览器或 Node.js 中使用的 JS 模块,以及serializeBinary/deserializeBinary背后的运行时源码原理与已知边界。
一、这份文档在仓库中的定位
coremltools 是苹果 Core ML 模型的转换、编辑与校验工具集,其仓库在 deps/protobuf 目录下整体内嵌了一份 Google Protocol Buffers 的源码树(含 C++、Java、Python、Objective-C、Ruby、JS 等多个语言运行时与protoc编译器)。本文要讲的 deps/protobuf/js/README.md 正是其中 JavaScript 运行时库的使用说明文档,它所描述的内容全部可以在同目录的源码与测试中得到印证:
- 运行时核心源码:
message.js、map.js、binary/子目录(arith.js、constants.js、decoder.js、encoder.js、reader.js、utils.js、writer.js); - 测试与构建配置:package.json(
google-protobuf3.19.0)、gulpfile.js、*_test.js系列 Jasmine 测试、commonjs/、compatibility_tests/、experimental/目录。
因此下文所有命令、参数和 API 描述,均以仓库内实际代码为准。
二、JS 运行时兼容的三种导入风格
根据 README 的说明,JavaScript Protocol Buffers 运行时库目前兼容:
- CommonJS 风格导入:例如
var protos = require('my-protos');; - Closure 风格导入:例如
goog.require('my.package.MyProto');; - ES6 风格导入尚未实现。在浏览器中使用时,需要通过 Browserify、webpack 或 Closure Compiler 等构建工具在编译期解析导入。
仓库内对应这两套测试:gulpfile.js中的test任务会"分别运行两份独立的测试副本——一份使用 Closure Compiler 风格导入,一份使用 CommonJS 导入"(见 gulpfile.js 的test_closure与test_commonjs串联),CommonJS 版产物统一输出到commonjs_out/目录。
三、两大核心组件:运行时库与 protoc
README 明确指出:在 JavaScript 中使用 Protocol Buffers 需要两个组件。
3.1 运行时库(runtime library)
- 最便捷的安装方式是
npm install google-protobuf; - 也可以直接使用本目录(deps/protobuf/js)中的文件;
- 不使用 npm 时(自 3.3.0 起),所需的文件位于
binary子目录下,即:arith.js、constants.js、decoder.js、encoder.js、map.js、message.js、reader.js、utils.js、writer.js。仓库中的 deps/protobuf/js/binary 目录正是这套文件,与文档描述一一对应。
仓库内嵌版本的信息可以从 package.json 确认:包名google-protobuf、版本3.19.0、入口文件google-protobuf.js、许可证 BSD-3-Clause,测试命令为node ./node_modules/gulp/bin/gulp.js test。
3.2 协议编译器 protoc
protoc负责把.proto文件翻译成.js文件。README 提醒:protoc 目前无法通过 npm 安装,需要从 Protocol Buffers 官方发布页面(Releases)下载预编译二进制,认准Downloads下的protoc-*.zip文件;也可以按顶层 README 的指引从源码自行编译。
四、环境搭建与运行自测
拿到 protoc 之后,README 建议先运行随项目提供的测试,验证环境可用。步骤:
- 从发布页下载 Protocol Buffers 源码压缩包并解压;
- 进入名为
js的目录(即本仓库的 deps/protobuf/js,其中包含package.json和一系列测试文件); - 依次执行:
$ npm install $ npm test如果 protoc 不在默认位置(默认假设是../src/protoc),可以显式指定(注意版本须与运行时兼容):
$ PROTOC=/usr/local/bin/protoc npm testPROTOC环境变量在构建脚本中确实是可配置的:gulpfile.js 的var protoc = process.env.PROTOC || '../src/protoc';与 README 描述完全一致。
测试通过的判定标准:会运行两份独立的测试副本(Closure 风格与 CommonJS 风格各一份),所有 CommonJS 产物可在commonjs_out/目录中查看。从源码看,构建与测试流水线如下(见 gulpfile.js):
- Closure 侧:
genproto_*_closure任务用--js_out=library=testproto_libs*,binary:.生成单文件库,deps任务调用closure-make-deps生成依赖图,最后由 Jasmine(JASMINE_CONFIG_PATH=jasmine.json)执行测试; - CommonJS 侧:
make_commonjs_out依次完成dist(生成google-protobuf.js)、各genproto_*_commonjs任务(输出到commonjs_out/)、commonjs_asserts/commonjs_testdeps(用 Closure Compiler 把断言与测试依赖打包成 CommonJS 模块)、以及rewrite_tests_for_commonjs.js对测试文件的重写,再在commonjs_out/下运行 Jasmine。
五、在自有项目中集成:两种导入风格
5.1 Closure imports
构建时运行形如:
$ protoc --js_out=library=myproto_libs,binary:. messages.proto base.protolibrary=myproto_libs使protoc生成单一输出文件(本例为myproto_libs.js);- 生成文件会对
.proto中定义的所有类型执行goog.provide(),例如测试中常见的:
goog.provide('proto.google.protobuf.DescriptorProto'); goog.provide('proto.google.protobuf.DescriptorProto.ExtensionRange'); goog.provide('proto.google.protobuf.DescriptorProto.ReservedRange'); goog.provide('proto.google.protobuf.EnumDescriptorProto'); goog.provide('proto.google.protobuf.EnumOptions');- 生成代码还会
goog.require()核心库中的许多类型,并依赖 Google Closure 库,因此必须保证goog.provide()/goog.require()机制能同时找到:生成的代码、本目录的核心库.js文件、以及 Google Closure 库本身。
之后即可按 Closure 风格导入类型:
goog.require('proto.my.package.MyMessage'); var message = proto.my.package.MyMessage();不熟悉 Closure / Closure Compiler 的读者,可以查阅 Google Closure 官方文档。
5.2 CommonJS imports
构建时运行形如:
$ protoc --js_out=import_style=commonjs,binary:. messages.proto base.proto- 与 Closure 模式不同,CommonJS 模式下
protoc会为每个输入文件各生成一个文件(本例为messages_pb.js与base_pb.js); - 生成代码依赖核心运行时,核心运行时位于名为
google-protobuf.js的文件中:- 从 npm 安装时,该文件已构建好、可直接使用;
- 从源码(如本仓库)使用时,需要先执行
gulp dist构建它——对应 gulpfile.js 的dist任务(先用protoc生成 well-known types,再用 Closure Compiler 以 commonjs/export.js 为入口打包出google-protobuf.js)。
之后即可按 CommonJS 风格导入:
var messages = require('./messages_pb'); var message = new messages.MyMessage();export.js中可以看到google-protobuf.js对外暴露的符号清单:Map、Message、BinaryReader、BinaryWriter、ExtensionFieldInfo、ExtensionFieldBinaryInfo,以及供生成代码内部使用的exportSymbol、inherits、object.extend、typeOf(见 commonjs/export.js)。仓库中的 commonjs/import_test.js 展示了require('google-protobuf')与require('./test7/test7_pb')的真实组合用法。
六、--js_out 标志详解
--js_out的完整语法为:
--js_out=[OPTIONS:]output_dir其中OPTIONS用逗号分隔;选项有两种形式:opt=val(带值)或opt(布尔开关)。所有可用选项的完整说明定义在GeneratorOptions结构体中(对应 protobuf 编译器源码src/google/protobuf/compiler/js/js_generator.h中的文档注释)。
README 给出的两个典型示例:
--js_out=library=myprotos_lib.js,binary:.:包含选项library=myprotos.lib.js与binary,输出到当前目录;import_style未指定,取默认值closure;--js_out=import_style=commonjs,binary:protos:包含选项import_style=commonjs与binary,输出到protos目录。
关键选项速查:
| 选项 | 取值 | 作用 |
|---|---|---|
import_style | closure(默认)/commonjs/commonjs_strict | 决定生成代码的导入风格;commonjs_strict不会把输出暴露到全局作用域 |
library | 库名 | 仅 Closure 模式生效,把多个.proto的生成代码合并为单文件输出 |
binary | 布尔开关 | 生成基于 binary wire format(二进制序列化)的代码 |
one_output_file_per_input_file | 布尔开关 | 每个输入.proto对应一个输出文件(gulpfile 的 well-known types 任务即使用它) |
另外在 gulpfile 中还可以看到--experimental_allow_proto3_optional标志(用于生成 proto3 optional 字段的测试代码),说明该版本 protoc 对 proto3 optional 需要显式开启。
七、运行时 API 快速上手
README 明确表示 JS 版的 API 尚未得到充分文档化,并给出了一段快速示例(这是整个 README 唯一的 API 代码示例,务必完整掌握):
var message = new MyMessage(); message.setName("John Doe"); message.setAge(25); message.setPhoneNumbers(["800-555-1212", "800-555-0000"]); // Serializes to a UInt8Array. var bytes = message.serializeBinary(); var message2 = MyMessage.deserializeBinary(bytes);要点解读:
- 生成代码为每个消息类型提供构造器(
new MyMessage())、字段 setter(setName/setAge/setPhoneNumbers)以及静态反序列化方法(MyMessage.deserializeBinary(bytes)); serializeBinary()返回的是Uint8Array,可无缝对接 ArrayBuffer 类场景(网络传输、WebSocket、fetch body 等);- 更多用法参见仓库测试:例如 binary/message_test.js 中
msg.serializeBinary()与deserializeBinary(serialized)的往返验证,以及 binary/proto_test.js 中TestAllTypes序列化/反序列化一致性断言(msg与msg.serializeBinary()反解结果相等)。commonjs/与compatibility_tests/下的测试同样围绕这套 API 展开。
八、源码级原理与使用边界
8.1 jspb.Message:基类与代码体积控制
所有 JS 版 proto 消息的基类为jspb.Message(见 message.js)。值得注意的设计决策:toObject、serializeBinary等常用方法并不定义在基类原型上,这是为了避免"项目里所有 proto 的未用代码"造成体积膨胀,方便 Closure Compiler 做死代码消除。因此 README 建议:若要在泛型函数里调用这些方法,把已知类型的方法作为参数传入(如KnownProtoClass.prototype.serializeBinary),或使用知道具体类型的 lambda 表达式。
基类还提供若干可裁剪的编译期开关(goog.define),用于进一步控制生成代码规模:
GENERATE_TO_OBJECT(默认开):是否生成toObject方法;GENERATE_FROM_OBJECT(默认仅测试代码开):是否生成fromObject方法;GENERATE_TO_STRING(默认开):是否生成toString方法;SERIALIZE_EMPTY_TRAILING_FIELDS(默认开):关闭序列化末尾空字段可减小序列化体积,代价是序列化前多遍历一次(为向后兼容默认开启,项目文档建议关闭)。
8.2 二进制编解码管线
生成代码的二进制序列化最终落到binary/目录的编解码器上:
- binary/writer.js:
jspb.BinaryWriter实现 Protocol Buffers 编码规范(wire format)中定义的全部编码器,内部以blocks_累积序列化分块、用BinaryEncoder缓冲当前正在序列化的片段,支持beginSubMessage/endSubMessage的子消息嵌套记账; - binary/reader.js:
jspb.BinaryReader实现全部 wire type 的解码器,内部持有BinaryDecoder游标,并维护一个全局实例缓存池(instanceCache_)以复用 reader 实例; - 中间层还包括
encoder.js(BinaryEncoder)、decoder.js(BinaryDecoder)、constants.js(wire type、字段类型等常量与类型定义)与utils.js; constants.js同时定义了jspb.ByteSource(ArrayBuffer | Uint8Array | Array<number> | string,字符串按 base64 编码)等核心类型,以及ReaderFunction/WriterFunction等回调函数类型约定。
8.3 64 位整数与 typed arrays 的硬边界
源码注释(binary/writer.js)给出了三条必须知晓的边界,README 未展开但实战中极易踩坑:
- JS 数字只有 53 位精度(双精度浮点),
BinaryWriter不会为超过2^53的整数值做特殊保精度处理——如果需要无损传递 64 位整数(例如 hash code),不要使用本库; - 本库依赖 typed arrays,不能用于不支持它们的旧浏览器;
- 为弥补精度缺口,binary/arith.js 提供了
Int64/UInt64实现——把 64 位整数表示为高、低两个 32 位(lo/hi),在 JS 的 53 位精度之上实现无损整数运算(如cmp、leftShift、rightShift)。
8.4 map 字段:jspb.Map
proto 的map字段在 JS 端由jspb.Map实现(map.js)。它紧密贴近 ES6 Map API,但刻意不依赖外部 polyfill 与 ES6 本身,因此可以运行在更广泛的 JS 环境中;仓库内maps_test.js与compatibility_tests/v3.1.0/maps_test.js专门覆盖 map 字段的序列化往返。
8.5 兼容性测试与实验性目录
- compatibility_tests 下保留了v3.0.0与v3.1.0两代快照,用于验证新版本运行时对旧版生成代码的向后兼容(如
compatibility_tests/v3.0.0/binary/proto_test.js、v3.0.0/proto3_test.js均与主版本测试结构一致); - experimental 目录包含实验性运行时(
runtime/kernel等)与代码体积基准(benchmarks/code_size),属于仍在演进中的部分,与稳定 API 相互独立。
九、结语与适用前提
在 coremltools 仓库的 deps/protobuf/js 中,README 描述的是一套完整可用的 JavaScript Protocol Buffers 方案:通过 npm 或binary/目录获取运行时,通过protoc --js_out按 Closure / CommonJS 风格生成代码,再以serializeBinary/deserializeBinary完成与任何语言侧 protobuf 实现互通的二进制序列化。需要特别说明的适用前提是:本文命令与参数针对仓库内嵌的google-protobuf 3.19.0版本;protoc需与运行时版本一致或兼容(README 原话);ES6 导入、64 位整数保精度、旧浏览器 typed arrays 等限制在相应版本内仍然成立。若需在本仓库内复现自测,请严格按第四节流程在js目录下执行npm install与npm test,并通过PROTOC环境变量指向与你所用运行时版本匹配的 protoc。
- AI 应用
- 机器学习
- 模型优化
【免费下载链接】coremltools
Core ML tools contain supporting tools for Core ML model conversion, editing, and validation.
相关推荐
Twirp 安装完全指南:运行时库、protoc 代码生成器与 Go 项目集成
Twirp 安装完全指南:运行时库、protoc 代码生成器与 Go 项目集成 Twirp 是一套以 protobuf 服务定义为核心的简单 RPC 框架:开发
RPC框架后端微服务μpb (upb) 详解:MongoDB 仓库内嵌的高性能 C 语言 Protocol Buffers 运行时
μpb upb 详解:MongoDB 仓库内嵌的高性能 C 语言 Protocol Buffers 运行时 μpb(常写作 upb )是一个用 C 语言实现的轻
数据库文档数据库后端使用protoc-gen-doc生成Protocol Buffers文档指南
使用protoc gen doc生成Protocol Buffers文档指南 项目概述 protoc gen doc是一个Protocol Buffers文档生
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考