diff --git a/bazel/closure_grpc_web_library.bzl b/bazel/closure_grpc_web_library.bzl index 252fff86..cc86c267 100644 --- a/bazel/closure_grpc_web_library.bzl +++ b/bazel/closure_grpc_web_library.bzl @@ -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() + + 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") - grpc_web_out_common_options = ",".join([ - "import_style={}".format(import_style), - "mode={}".format(mode), - ]) + 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", diff --git a/javascript/net/grpc/web/grpc_generator.cc b/javascript/net/grpc/web/grpc_generator.cc index fda3d96a..f9341e65 100644 --- a/javascript/net/grpc/web/grpc_generator.cc +++ b/javascript/net/grpc/web/grpc_generator.cc @@ -1471,6 +1471,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(); @@ -1485,6 +1532,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: @@ -1492,6 +1540,7 @@ class GeneratorOptions { string mode_; ImportStyle import_style_; bool generate_dts_; + bool generate_closure_es6_; bool multiple_files_; }; @@ -1500,6 +1549,7 @@ GeneratorOptions::GeneratorOptions() mode_(""), import_style_(ImportStyle::CLOSURE), generate_dts_(false), + generate_closure_es6_(false), multiple_files_(false){} bool GeneratorOptions::ParseFromOptions(const string& parameter, @@ -1519,6 +1569,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) { @@ -1749,6 +1802,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 es6_output( + context->Open(es6_file_name)); + Printer es6_printer(es6_output.get(), '$'); + + PrintGrpcWebClosureES6File(&es6_printer, file); + } + return true; } }; diff --git a/net/grpc/gateway/examples/echo/BUILD.bazel b/net/grpc/gateway/examples/echo/BUILD.bazel index df2f7b77..76122fff 100644 --- a/net/grpc/gateway/examples/echo/BUILD.bazel +++ b/net/grpc/gateway/examples/echo/BUILD.bazel @@ -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", + ], +)