Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: introduce ts_proto_library #412

Merged
merged 17 commits into from
Aug 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .bazelignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
e2e/
bazel-output-base
examples/connect_node/node_modules
examples/node_modules
examples/proto_grpc/node_modules
examples/linked/node_modules
examples/linked_consumer/node_modules
examples/linked_tsconfig/node_modules
Expand Down
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
docs/*.md
**/pnpm-lock.yaml
**/*_pb.d.ts
**/*_connect.d.ts
7 changes: 7 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ bazel_dep(name = "rules_nodejs", version = "5.8.2", dev_dependency = True)
bazel_dep(name = "aspect_rules_js", version = "1.29.2")
bazel_dep(name = "aspect_bazel_lib", version = "1.29.2")

# Similar to rules_python/MODULE.bazel, see https://github.com/bazelbuild/rules_python/pull/832
# These are loaded only when using ts_proto_library
bazel_dep(name = "rules_proto", version = "5.3.0-21.7")
# Only needed because rules_proto doesn't provide the protoc toolchain yet.
# TODO(alex/sahin): remove in the future
bazel_dep(name = "protobuf", version = "21.7", repo_name = "com_google_protobuf")

rules_ts_ext = use_extension(
"@aspect_rules_ts//ts:extensions.bzl",
"ext",
Expand Down
6 changes: 6 additions & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,9 @@ npm_repositories()
# You can verify the typescript version used by Bazel:
# bazel run -- @npm_typescript//:tsc --version
rules_ts_dependencies(ts_version_from = "@npm//examples:typescript/resolved.json")

###########################################
# Protobuf rules to test ts_proto_library
load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies")

rules_proto_dependencies()
5 changes: 5 additions & 0 deletions docs/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ stardoc_with_diff_test(
bzl_library_target = "//ts:defs",
)

stardoc_with_diff_test(
name = "proto",
bzl_library_target = "//ts:proto",
)

stardoc_with_diff_test(
name = "repositories",
bzl_library_target = "//ts:repositories",
Expand Down
64 changes: 64 additions & 0 deletions docs/proto.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<!-- Generated with Stardoc: http://skydoc.bazel.build -->

# Protocol Buffers and gRPC (UNSTABLE)

**UNSTABLE API**: contents of this page are not subject to our usual semver guarantees.
We may make breaking changes in any release.
Please try this API and provide feedback.
We intend to promote it to a stable API in a minor release, possibly as soon as v2.1.0.

`ts_proto_library` uses the Connect library from bufbuild, and supports both Web and Node.js:

- https://connectrpc.com/docs/web/getting-started
- https://connectrpc.com/docs/node/getting-started

This Bazel integration follows the "Local Generation" mechanism described at
https://connectrpc.com/docs/web/generating-code#local-generation,
using the `@bufbuild/protoc-gen-connect-es` and `@bufbuild/protoc-gen-es` packages as plugins to protoc.

Note: this API surface is not included in `defs.bzl` to avoid eager loads of rules_proto for all rules_ts users.

Installation
------------

If you install rules_ts in `WORKSPACE`, you'll need to install the deps of rules_proto, like this:

```
load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies")

rules_proto_dependencies()
```

If you use bzlmod/`MODULE.bazel` then no extra install is required.

Future work
-----------

- Add support for generating the `ts_proto_library` targets in [aspect configure](https://docs.aspect.build/v/cli/commands/aspect_configure)
- Allow users to choose other plugins. We intend to wait until http://github.com/bazelbuild/rules_proto supports protoc plugins.
- Allow users to control the output format. Currently it is hard-coded to `js+dts`, and the JS output uses ES Modules.


<a id="ts_proto_library"></a>

## ts_proto_library

<pre>
ts_proto_library(<a href="#ts_proto_library-name">name</a>, <a href="#ts_proto_library-node_modules">node_modules</a>, <a href="#ts_proto_library-has_services">has_services</a>, <a href="#ts_proto_library-copy_files">copy_files</a>, <a href="#ts_proto_library-files_to_copy">files_to_copy</a>, <a href="#ts_proto_library-kwargs">kwargs</a>)
</pre>

A macro to generate JavaScript code and TypeScript typings from .proto files.

**PARAMETERS**


| Name | Description | Default Value |
| :------------- | :------------- | :------------- |
| <a id="ts_proto_library-name"></a>name | name of resulting ts_proto_library target | none |
| <a id="ts_proto_library-node_modules"></a>node_modules | Label pointing to the linked node_modules target where @bufbuild/protoc-gen-es is linked, e.g. //:node_modules. Since the generated code depends on @bufbuild/protobuf, this package must also be linked. If <code>has_services = True</code> then @bufbuild/proto-gen-connect-es should be linked as well. | none |
| <a id="ts_proto_library-has_services"></a>has_services | whether the proto file contains a service, and therefore *_connect.{js,d.ts} should be written. | <code>True</code> |
| <a id="ts_proto_library-copy_files"></a>copy_files | whether to copy the resulting .d.ts files back to the source tree, for the editor to locate them. | <code>True</code> |
| <a id="ts_proto_library-files_to_copy"></a>files_to_copy | which files from the protoc output to copy. By default, scans for *.proto in the current package and replaces with the typical output filenames. | <code>None</code> |
| <a id="ts_proto_library-kwargs"></a>kwargs | additional named arguments to the ts_proto_library rule | none |


2 changes: 1 addition & 1 deletion e2e/bzlmod/.bazelrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# NB: we don't import common bazelrc shared with e2e workspaces

common --experimental_enable_bzlmod
common --enable_bzlmod
common --@aspect_rules_ts//ts:skipLibCheck=honor_tsconfig
20 changes: 20 additions & 0 deletions e2e/bzlmod/BUILD
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
load("@aspect_rules_ts//ts:defs.bzl", "ts_project")
load("@bazel_skylib//rules:build_test.bzl", "build_test")
load("@rules_proto//proto:defs.bzl", "proto_library")
load("@aspect_rules_ts//ts:proto.bzl", "ts_proto_library")
load("@npm//:defs.bzl", "npm_link_all_packages")

package(default_visibility = ["//visibility:public"])

npm_link_all_packages(name = "node_modules")

proto_library(
name = "foo_proto",
srcs = ["foo.proto"],
)

ts_proto_library(
name = "foo_ts_proto",
has_services = False,
copy_files = False,
node_modules = ":node_modules",
proto = ":foo_proto",
)

ts_project(
name = "ts",
Expand Down
8 changes: 8 additions & 0 deletions e2e/bzlmod/MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,20 @@ module(
bazel_dep(name = "aspect_rules_js", version = "1.29.2", dev_dependency = True)
bazel_dep(name = "aspect_rules_ts", version = "0.0.0", dev_dependency = True)
bazel_dep(name = "bazel_skylib", version = "1.4.1", dev_dependency = True)
bazel_dep(name = "rules_proto", version = "5.3.0-21.7", dev_dependency = True)

local_path_override(
module_name = "aspect_rules_ts",
path = "../..",
)

npm = use_extension("@aspect_rules_js//npm:extensions.bzl", "npm", dev_dependency = True)
npm.npm_translate_lock(
name = "npm",
pnpm_lock = "//:pnpm-lock.yaml",
)
use_repo(npm, "npm")

rules_ts_ext = use_extension(
"@aspect_rules_ts//ts:extensions.bzl",
"ext",
Expand Down
3 changes: 3 additions & 0 deletions e2e/bzlmod/foo.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
syntax = "proto3";

message Empty{}
4 changes: 4 additions & 0 deletions e2e/bzlmod/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"dependencies": {
"@bufbuild/protobuf": "1.3.0"
},
"devDependencies": {
"@bufbuild/protoc-gen-es": "1.3.0",
"typescript": "4.8.4"
}
}
97 changes: 97 additions & 0 deletions e2e/bzlmod/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 35 additions & 0 deletions examples/connect_node/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
load("@aspect_rules_js//js:defs.bzl", "js_binary")
load("@aspect_rules_ts//ts:defs.bzl", "ts_project")
load("@npm//:defs.bzl", "npm_link_all_packages")

npm_link_all_packages()

ts_project(
name = "connect_node",
srcs = [
"connect.ts",
"server.ts",
],
deps = [
":node_modules/@bufbuild/connect",
":node_modules/@bufbuild/connect-fastify",
":node_modules/@types/node",
":node_modules/fastify",
"//examples/connect_node/proto:eliza_ts_proto",
],
)

# Try it out:
# $ bazel run server
# $ curl \
# --header 'Content-Type: application/json' \
# --data '{"sentence": "I feel happy."}' \
# http://localhost:8080/connectrpc.eliza.v1.ElizaService/Say
js_binary(
name = "server",
data = [
":connect_node",
":node_modules/@bufbuild/protobuf",
],
entry_point = "server.js",
)
4 changes: 4 additions & 0 deletions examples/connect_node/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Connect-Node example

This example follows https://connectrpc.com/docs/node/getting-started, showing how to do exactly
the same application under Bazel.
14 changes: 14 additions & 0 deletions examples/connect_node/connect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ConnectRouter } from '@bufbuild/connect'
import { ElizaService } from './proto/eliza_connect.js'

export default (router: ConnectRouter) =>
// registers connectrpc.eliza.v1.ElizaService
// TODO(alexeagle): why do we need to Put an `any` on it?
router.service(ElizaService as any, {
// implements rpc Say
async say(req) {
return {
sentence: `You said: ${req.sentence}`,
}
},
})
18 changes: 18 additions & 0 deletions examples/connect_node/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"dependencies": {
"@bufbuild/buf": "^1.26.1",
"@bufbuild/connect": "^0.13.0",
"@bufbuild/connect-fastify": "^0.13.0",
"@bufbuild/protobuf": "^1.3.0",
"fastify": "^4.21.0"
},
"devDependencies": {
"@types/node": "18.11.9",
"@bufbuild/protoc-gen-connect-es": "^0.13.0",
"@bufbuild/protoc-gen-es": "^1.3.0"
},
"type": "module",
"scripts": {
"start": "bazel run server"
}
}
22 changes: 22 additions & 0 deletions examples/connect_node/proto/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
load("@aspect_rules_js//js:defs.bzl", "js_library")
load("@rules_proto//proto:defs.bzl", "proto_library")
load("@aspect_rules_ts//ts:proto.bzl", "ts_proto_library")

package(default_visibility = ["//visibility:public"])

proto_library(
name = "eliza_proto",
srcs = ["eliza.proto"],
)

ts_proto_library(
name = "eliza_ts_proto",
node_modules = "//examples/connect_node:node_modules",
proto = ":eliza_proto",
)

js_library(
name = "proto",
srcs = [":package.json"],
deps = ["eliza_ts_proto"],
)
15 changes: 15 additions & 0 deletions examples/connect_node/proto/eliza.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
syntax = "proto3";

package connectrpc.eliza.v1;

message SayRequest {
string sentence = 1;
}

message SayResponse {
string sentence = 1;
}

service ElizaService {
rpc Say(SayRequest) returns (SayResponse) {}
}
Loading
Loading