GopherJS support for Protocol Buffers and gRPC-Web
Generate GopherJS interfaces to ProtobufJS and gRPC Web from your proto files.
Attribution
Most of the code is based on a fork of Googles
protoc-gen-go code generator,
with heavy modifications to output GopherJS compatible structs.
Compatibility
Only proto files with syntax="proto3";
are supported.
Installation
To use this software, you must:
-
Install the protocol buffer compiler, protoc
:
https://developers.google.com/protocol-buffers/
-
Install the Go compiler and tools:
https://golang.org/
-
Grab the code from the repository and install the proto package.
$ go get -u github.com/johanbrandhorst/protobuf/protoc-gen-gopherjs
The compiler plugin, protoc-gen-gopherjs
, will be installed in $GOBIN
,
defaulting to $GOPATH/bin
. It must be in your $PATH
for the protocol
compiler, protoc
, to find it.
This package generates files that import the
GopherJS ProtobufJS bindings. The
generated interface is designed to be as similar as possible to that of files
generated with protoc-gen-go
, with a few differences:
- Getters and Setters instead of properties on message structs.
This is due to the design of ProtobufJS which similarly uses getters
and setters instead of nested objects.
- "Factory functions" for initializing new message structs.
This is mostly as a convenience to the user. Oneof fields still
have to be set separately and will default to unset.
Serialize
and Deserialize
methods for each type.
Instead of proto.Marshal
and proto.Unmarshal
, these are used
to serialize to and from binary.
Usage
Once the software is installed, there are two steps to using it.
First you must compile the protocol buffer definitions and then import
them into your program.
To compile the protocol buffer definition, run protoc with the --gopherjs_out
and --js_out
parameters set to the directory you want to output the GopherJS code to.
The ProtobufJS must be run with the following parameters: import_style=commonjs,binary
$ protoc --gopherjs_out=. --js_out=import_style=commonjs,binary:. *.proto
The generated files will be suffixed .pb.gopherjs.go
and pb.js
.
The generated JS file must be manually edited to be compatible;
- Each instance of
require('google-protobuf')
replaced with $global
- Each instance of
require('google-protobuf/.*')
replace with $global.proto.google.protobuf
- Any other
require()
instances must be modified to include correct objects. This will depend on the configuration of the proto files that were used when generating, but should be fairly straightforward given the examples here.
- The
export
statements at the end of the files must be removed.
- The generated JS file must be renamed
*.inc.js
to be properly included by GopherJS.
The reason for these modifications is because the $global
object binds together all the
definitions to one root object, without the need for require
(this is subject to improval if someone with better JS knowledge than I knows a better way).
Here's a couple of handy sed
snippets to accomplish this:
# Replace top level import with global reference
$ sed -i "s;require('google-protobuf');\$global;g" <generated_js_file_pb.js>
# Replace any well known type imports with correct namespace
$ sed -i -E "s;require\('google-protobuf/.*'\);\$global.proto.google.protobuf;g" <generated_js_file_pb.js>
# Remove export statement
$ sed -i -E "s/goog\.object\.extend\(exports, proto\..*\);$//g" <generated_js_file_pb.js>
An example
Consider file test.proto
, containing
syntax="proto3";
package example;
message Test {
string label = 1;
int32 type = 2;
int64 reps = 3;
}
Generate it:
$ protoc --gopherjs_out=. --js_out=import_style=commonjs,binary:. test.proto
This generates the following client code (abbreviated):
// Code generated by protoc-gen-gopherjs. DO NOT EDIT.
// source: test.proto
package example
import js "github.com/gopherjs/gopherjs/js"
import jspb "github.com/johanbrandhorst/protobuf/jspb"
type Test struct {
*js.Object
}
// New creates a new Test.
func (m *Test) New(label string, _type int32, reps int64) *Test {
m := &Test{
Object: js.Global.Get("proto").Get("example").Get("Test").New([]interface{}{
label,
_type,
reps,
}),
}
return m
}
// Setters and Getters
// ....
// Serialize marshals Test to a slice of bytes.
func (m *Test) Serialize() (rawBytes []byte, err error) {
return jspb.Serialize(m)
}
// Deserialize unmarshals a Test from a slice of bytes.
func (m *Test) Deserialize(rawBytes []byte) (*Test, error) {
obj, err := jspb.Deserialize(js.Global.Get("proto").Get("example").Get("Test"), rawBytes)
if err != nil {
return nil, err
}
return &Test{
Object: obj,
}, nil
}
In your client code, you can use the library like so:
package main
import "github.com/youruser/yourrepo/example"
func main() {
t := new(example.Test).New("Label", 1234, 5678)
rawBytes, err := t.Serialize()
if err != nil {
panic(err)
}
t1, err := new(example.Test).Deserialize(rawBytes)
if err != nil {
panic(err)
}
}
Parameters
To pass extra parameters to the plugin, use a comma-separated
parameter list separated from the output directory by a colon:
$ protoc --gopherjs_out=plugins=grpc,import_path=mypackage:. *.proto
import_prefix=xxx
- a prefix that is added onto the beginning of
all imports. Useful for things like generating protos in a
subdirectory, or regenerating vendored protobufs in-place.
import_path=foo/bar
- used as the package if no input files
declare go_package
. If it contains slashes, everything up to the
rightmost slash is ignored.
plugins=plugin1+plugin2
- specifies the list of sub-plugins to
load. The only plugin in this repo is grpc
.
Mfoo/bar.proto=quux/shme
- declares that foo/bar.proto
is
associated with Go package quux/shme
. This is subject to the
import_prefix
parameter.
Generate gRPC-Web bindings
If a proto file specifies RPC services, protoc-gen-gopherjs
can be instructed to
generate code compatible with the GopherJS gRPC-Web bindings.
To do this, pass the plugins
parameter to protoc-gen-gopherjs
:
$ protoc --gopherjs_out=plugins=grpc:. *.proto