Blog

通过 ESM 引入 gRPC

2024-06-01

#gRPC
#Node.js
#水

遇到语焉不详的文档踩了一堆坑,示例有用但不多,还是记录一下免得以后忘了……

安装依赖

yarn add -D grpc-tools @grpc/grpc-js @grpc/proto-loader google-protobuf

编辑 proto 文件

仅供参考

syntax = "proto3";

package pb;

service PoolService {
    rpc GetContents(Query) returns (Response);
}

message Content {
    string content = 1;
    int64 expire = 2;
}

message Query {
    int64 count = 1;
}

message Response {
    repeated Content data = 1;
}

静态生成

npx grpc_tools_node_protoc --js_out=import_style=es6,binary:. --grpc_out=. --proto_path=. mypb.proto
  • 全部目录我都写了 .,为的是生成在当前文件夹,未来再根据需要移到其他目录,所以这些值需要根据实际情况来填

ESM 支持

grpc-tools 对 esm 的支持属于是一言难尽,只能生成以后自己改了

  • mypb_pb.js
    • 将开头的 require 引入改成 import
    • 在末尾导出 proto.pb(这个名字取决于前面填的包名)
  • mypb_grpc_pb.js
    • 将开头的 require 引入改成 import
    • 在结尾的相关函数使用 export 导出
// mypb_pb.js
/// begin
+import jspb from 'google-protobuf'
+var goog = jspb

/// end
+export default proto.pb

// mypb_grpc_pb.js
/// begin
-var grpc = require('grpc');
-var mypb_pb = require('./mypb_pb.js');
+import grpc from '@grpc/grpc-js'
+import mypb_pb from './mypb_pb.js'

/// end
-var PoolServiceService = exports.PoolServiceService = {
+export const PoolServiceService = {
    getContents: {
    path: '/pb.PoolService/GetContents',
    requestStream: false,
    responseStream: false,
    requestType: mypb_pb.Query,
    responseType: mypb_pb.Response,
    requestSerialize: serialize_pb_Query,
    requestDeserialize: deserialize_pb_Query,
    responseSerialize: serialize_pb_Response,
    responseDeserialize: deserialize_pb_Response,
  },
};

-exports.PoolServiceClient = grpc.makeGenericClientConstructor(PoolServiceService);
+export const PoolServiceClient = grpc.makeGenericClientConstructor(PoolServiceService);

* 2024-06-18 补充

根据上面的 diff 文件,配合 GPT 写出了以下修改代码

#!/bin/bash

# the `package` from .proto file
PB_PACKAGENAME="pb"

npx grpc_tools_node_protoc --js_out=import_style=es6,binary:. --grpc_out=. --proto_path=. *.proto

# ends with _pb.js
for file in $(find . -maxdepth 1 -name "*_pb.js" ! -name "*_grpc_pb.js"); do
  sed -i '1i import jspb from '\''google-protobuf'\''\nlet goog = jspb' "$file"

  echo "export default proto.$PB_PACKAGENAME" >> "$file"
done

# ends with _grpc_pb.js
for file in *_grpc_pb.js; do
  PREFIX=$(basename "$file" "_grpc_pb.js")

  sed -i "s|var grpc = require('grpc');|import grpc from '@grpc/grpc-js'|g" "$file"
  sed -i "s|var ${PREFIX}_pb = require('./${PREFIX}_pb.js');|import ${PREFIX}_pb from './${PREFIX}_pb.js'|g" "$file"

  SERVICE_VAR=$(grep -oP "var \K\w+(?= = exports\.)" "$file")
  sed -i "s|var $SERVICE_VAR = exports\.$SERVICE_VAR =|export const $SERVICE_VAR =|g" "$file"

  sed -i "s|exports\.\(\w\+\) =|export const \1 =|g" "$file"
done

使用

import grpc from '@grpc/grpc-js'
import * as grpc_pb from 'mypb_grpc_pb.js'
import pb from 'mypb_pb.js'

const client = new grpc_pb.PoolService('localhost:50051', grpc.credentials.createInsecure())

let request = new pb.GetContents()
request.setCount(10)
client.getContents(request, (error, response) => {
  if (error) {
    console.error('Error:', error)
  } else {
    console.log('Response:', response)
  }
})

杂项

  • 发送请求时会直接使用环境变量里面的 http 代理
  • 生成的文件里面莫名其妙的 goog 其实就是 google-protobuf
  • 没有服务端代码的原因是服务端是 Golang 写的,文档齐全配 GPT 三两下就弄好了
  • 还有一些奇怪的细节我已经记不清楚了

感谢


评论区