Add experimental ES6 import style

This change adds a new `import_style` that emits ES6 modules.
For now, this only re-exports the symbols from `import_style=closure`.

In the future, this will no longer require emitting google modules
(`goog.provide`). If protobuf ever adds support for emitting ES6
modules, we will use them instead of the `goog.provided`'d versions
as well.

Note that the Bazel integration is currently broken because of a
limitation in `rules_closure`.
This commit is contained in:
Yannic Bonenberger 2020-05-26 23:09:58 +02:00 committed by Stanley Cheung
parent f516ccbf3c
commit a055960f80
3 changed files with 137 additions and 40 deletions

View File

@ -36,11 +36,16 @@ def _proto_include_path(proto):
def _proto_include_paths(protos):
return [_proto_include_path(proto) for proto in protos]
def _generate_closure_grpc_web_src_progress_message(name):
def _generate_closure_grpc_web_src_progress_message(proto):
# TODO(yannic): Add a better message?
return "Generating GRPC Web %s" % name
return "Generating GRPC Web for %s" % proto
def _assert(condition, message):
if not condition:
fail(message)
def _generate_closure_grpc_web_srcs(
label,
actions,
protoc,
protoc_gen_grpc_web,
@ -48,48 +53,56 @@ def _generate_closure_grpc_web_srcs(
mode,
sources,
transitive_sources):
all_sources = [src for src in sources] + [src for src in transitive_sources.to_list()]
proto_include_paths = [
"-I%s" % p
for p in _proto_include_paths(
[f for f in all_sources],
)
]
args = actions.args()
grpc_web_out_common_options = ",".join([
"import_style={}".format(import_style),
"mode={}".format(mode),
])
args.add("--plugin", "protoc-gen-grpc-web=" + protoc_gen_grpc_web.path)
args.add_all(_proto_include_paths(transitive_sources.to_list()), format_each = "-I%s")
args.add("--grpc-web_opt", "mode=" + mode)
if "es6" == import_style:
args.add("--grpc-web_opt", "import_style=experimental_closure_es6")
else:
args.add("--grpc-web_opt", "import_style=" + import_style)
root = None
files = []
es6_files = []
for src in sources:
name = "{}.grpc.js".format(
".".join(src.path.split("/")[-1].split(".")[:-1]),
)
js = actions.declare_file(name)
basename = src.basename[:-(len(src.extension) + 1)]
js = actions.declare_file(basename + "_grpc_web_pb.js", sibling = src)
files.append(js)
args = proto_include_paths + [
"--plugin=protoc-gen-grpc-web={}".format(protoc_gen_grpc_web.path),
"--grpc-web_out={options},out={out_file}:{path}".format(
options = grpc_web_out_common_options,
out_file = name,
path = js.path[:js.path.rfind("/")],
),
src.path,
]
actions.run(
tools = [protoc_gen_grpc_web],
inputs = all_sources,
outputs = [js],
executable = protoc,
arguments = args,
progress_message =
_generate_closure_grpc_web_src_progress_message(name),
_assert(
((root == None) or (root == js.root.path)),
"proto sources do not have the same root: '{}' != '{}'".format(root, js.root.path),
)
root = js.root.path
return files
if "es6" == import_style:
es6 = actions.declare_file(basename + ".pb.grpc-web.js", sibling = src)
es6_files.append(es6)
_assert(root == es6.root.path, "ES6 file should have same root: '{}' != '{}'".format(root, es6.root.path))
_assert(root, "At least one source file is required")
args.add("--grpc-web_out", root)
args.add_all(sources)
actions.run(
tools = [protoc_gen_grpc_web],
inputs = transitive_sources,
outputs = files + es6_files,
executable = protoc,
arguments = [args],
progress_message =
_generate_closure_grpc_web_src_progress_message(str(label)),
)
return files, es6_files
_error_multiple_deps = "".join([
"'deps' attribute must contain exactly one label ",
@ -103,7 +116,8 @@ def _closure_grpc_web_library_impl(ctx):
fail(_error_multiple_deps, "deps")
proto_info = ctx.attr.deps[0][ProtoInfo]
srcs = _generate_closure_grpc_web_srcs(
srcs, es6_srcs = _generate_closure_grpc_web_srcs(
label = ctx.label,
actions = ctx.actions,
protoc = ctx.executable._protoc,
protoc_gen_grpc_web = ctx.executable._protoc_gen_grpc_web,
@ -117,7 +131,7 @@ def _closure_grpc_web_library_impl(ctx):
deps.append(ctx.attr._runtime)
library = create_closure_js_library(
ctx = ctx,
srcs = srcs,
srcs = srcs + es6_srcs,
deps = deps,
suppress = [
"misplacedTypeAnnotation",
@ -149,7 +163,13 @@ closure_grpc_web_library = rule(
),
"import_style": attr.string(
default = "closure",
values = ["closure"],
values = [
"closure",
# This is experimental and requires closure-js.
# We reserve the right to do breaking changes at any time.
"es6",
],
),
"mode": attr.string(
default = "grpcwebtext",

View File

@ -1468,6 +1468,53 @@ void PrintMultipleFilesMode(const FileDescriptor* file, string file_name,
printer2.Print("}); // goog.scope\n\n");
}
void PrintClosureES6Imports(
Printer* printer, const FileDescriptor* file, string package_dot) {
for (int i = 0; i < file->service_count(); ++i) {
const ServiceDescriptor* service = file->service(i);
string service_namespace = "proto." + package_dot + service->name();
printer->Print(
"import $service_name$Client_import from 'goog:$namespace$';\n",
"service_name", service->name(),
"namespace", service_namespace + "Client");
printer->Print(
"import $service_name$PromiseClient_import from 'goog:$namespace$';\n",
"service_name", service->name(),
"namespace", service_namespace + "PromiseClient");
}
printer->Print("\n\n\n");
}
void PrintGrpcWebClosureES6File(Printer* printer, const FileDescriptor* file) {
string package_dot = file->package().empty() ? "" : file->package() + ".";
printer->Print(
"// GENERATED CODE -- DO NOT EDIT!\n"
"\n"
"/**\n"
" * @fileoverview gRPC-Web generated client stub for '$file$'\n"
" */\n"
"\n"
"\n",
"file", file->name());
PrintClosureES6Imports(printer, file, package_dot);
for (int i = 0; i < file->service_count(); ++i) {
const ServiceDescriptor* service = file->service(i);
string service_namespace = "proto." + package_dot + service->name();
printer->Print(
"export const $name$Client = $name$Client_import;\n",
"name", service->name());
printer->Print(
"export const $name$PromiseClient = $name$PromiseClient_import;\n",
"name", service->name());
}
}
class GeneratorOptions {
public:
GeneratorOptions();
@ -1482,6 +1529,7 @@ class GeneratorOptions {
string mode() const { return mode_; }
ImportStyle import_style() const { return import_style_; }
bool generate_dts() const { return generate_dts_; }
bool generate_closure_es6() const { return generate_closure_es6_; }
bool multiple_files() const { return multiple_files_; }
private:
@ -1489,6 +1537,7 @@ class GeneratorOptions {
string mode_;
ImportStyle import_style_;
bool generate_dts_;
bool generate_closure_es6_;
bool multiple_files_;
};
@ -1497,6 +1546,7 @@ GeneratorOptions::GeneratorOptions()
mode_(""),
import_style_(ImportStyle::CLOSURE),
generate_dts_(false),
generate_closure_es6_(false),
multiple_files_(false){}
bool GeneratorOptions::ParseFromOptions(const string& parameter,
@ -1516,6 +1566,9 @@ bool GeneratorOptions::ParseFromOptions(
} else if ("import_style" == option.first) {
if ("closure" == option.second) {
import_style_ = ImportStyle::CLOSURE;
} else if ("experimental_closure_es6" == option.second) {
import_style_ = ImportStyle::CLOSURE;
generate_closure_es6_ = true;
} else if ("commonjs" == option.second) {
import_style_ = ImportStyle::COMMONJS;
} else if ("commonjs+dts" == option.second) {
@ -1744,6 +1797,16 @@ class GrpcCodeGenerator : public CodeGenerator {
PrintGrpcWebDtsFile(&grpcweb_dts_printer, file);
}
if (generator_options.generate_closure_es6()) {
string es6_file_name = StripProto(file->name()) + ".pb.grpc-web.js";
std::unique_ptr<ZeroCopyOutputStream> es6_output(
context->Open(es6_file_name));
Printer es6_printer(es6_output.get(), '$');
PrintGrpcWebClosureES6File(&es6_printer, file);
}
return true;
}
};

View File

@ -1,3 +1,4 @@
load("@io_bazel_rules_closure//closure:defs.bzl", "closure_js_binary")
load("@rules_proto//proto:defs.bzl", "proto_library")
load("//bazel:closure_grpc_web_library.bzl", "closure_grpc_web_library")
@ -9,8 +10,21 @@ proto_library(
)
closure_grpc_web_library(
name = "echo",
name = "echo_es6",
import_style = "es6",
deps = [
":echo_proto",
],
)
# A (very simple) inegration test.
closure_js_binary(
name = "echo_es6_bin",
entry_points = [
"goog:proto.grpc.gateway.testing.EchoServiceClient",
"/net/grpc/gateway/examples/echo/echo.pb.grpc-web.js",
],
deps = [
":echo_es6",
],
)