diff --git a/WORKSPACE b/WORKSPACE index 8ddd223a4f..6fca652339 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -297,3 +297,9 @@ bazel_binaries(versions = SUPPORTED_BAZEL_VERSIONS) load("@npm_bazel_labs//:package.bzl", "npm_bazel_labs_dependencies") npm_bazel_labs_dependencies() + +load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains") + +rules_proto_dependencies() + +rules_proto_toolchains() diff --git a/examples/protocol_buffers/BUILD.bazel b/examples/protocol_buffers/BUILD.bazel index ddaf9591e6..d5bd4990ac 100644 --- a/examples/protocol_buffers/BUILD.bazel +++ b/examples/protocol_buffers/BUILD.bazel @@ -11,6 +11,14 @@ proto_library( srcs = ["tire.proto"], ) +ts_proto_library( + # The result will be "tire.d.ts" named after this target. + # We could use the output_name attribute if we want the output named + # differently than the target. + name = "tire", + proto = ":tire_proto", +) + proto_library( name = "car_proto", srcs = ["car.proto"], @@ -22,7 +30,7 @@ ts_proto_library( # We could use the output_name attribute if we want the output named # differently than the target. name = "car", - deps = [":car_proto"], + proto = ":car_proto", ) ts_config( @@ -38,16 +46,17 @@ ts_library( tsconfig = "//:tsconfig-test", deps = [ ":car", + ":tire", "@npm//@types/jasmine", - "@npm//@types/long", "@npm//@types/node", - "@npm//long", ], ) karma_web_test_suite( name = "test", - bootstrap = ["@npm_bazel_labs//protobufjs:bootstrap_scripts"], + srcs = [ + "@npm_bazel_labs//grpc_web:bootstrap_scripts", + ], browsers = [ "@io_bazel_rules_webtesting//browsers:chromium-local", "@io_bazel_rules_webtesting//browsers:firefox-local", @@ -64,8 +73,8 @@ ts_library( ts_devserver( name = "devserver", - bootstrap = ["@npm_bazel_labs//protobufjs:bootstrap_scripts"], entry_module = "examples_protocol_buffers/app", + scripts = ["@npm_bazel_labs//grpc_web:bootstrap_scripts"], deps = [":app"], ) @@ -75,7 +84,12 @@ rollup_bundle( config_file = "rollup.config.js", entry_point = ":app.ts", format = "iife", - deps = [":app"], + deps = [ + ":app", + "@npm//:node_modules", + "@npm//rollup-plugin-commonjs", + "@npm//rollup-plugin-node-resolve", + ], ) terser_minified( @@ -83,27 +97,11 @@ terser_minified( src = ":bundle", ) -# Needed because the prodserver only loads static files that appear under this -# package. -genrule( - name = "protobufjs", - srcs = [ - "@build_bazel_rules_typescript_protobufs_compiletime_deps//:node_modules/protobufjs/dist/minimal/protobuf.min.js", - "@build_bazel_rules_typescript_protobufs_compiletime_deps//:node_modules/long/dist/long.js", - ], - outs = [ - "protobuf.min.js", - "long.js", - ], - cmd = "outs=($(OUTS)); d=$$(dirname $${outs[0]}); for s in $(SRCS); do cp $$s $$d; done", -) - http_server( name = "prodserver", data = [ "index.html", ":bundle.min", - ":protobufjs", ], ) diff --git a/examples/protocol_buffers/WORKSPACE b/examples/protocol_buffers/WORKSPACE index f6cf1d136f..074485289a 100644 --- a/examples/protocol_buffers/WORKSPACE +++ b/examples/protocol_buffers/WORKSPACE @@ -32,6 +32,15 @@ http_archive( urls = ["https://github.com/protocolbuffers/protobuf/archive/v3.11.4.tar.gz"], ) +http_archive( + name = "rules_proto", + sha256 = "4d421d51f9ecfe9bf96ab23b55c6f2b809cbaf0eea24952683e397decfbd0dd0", + strip_prefix = "rules_proto-f6b8d89b90a7956f6782a4a3609b2f0eee3ce965", + urls = [ + "https://github.com/bazelbuild/rules_proto/archive/f6b8d89b90a7956f6782a4a3609b2f0eee3ce965.tar.gz", + ], +) + load("@build_bazel_rules_nodejs//:index.bzl", "yarn_install") yarn_install( @@ -56,6 +65,12 @@ load("@npm_bazel_labs//:package.bzl", "npm_bazel_labs_dependencies") npm_bazel_labs_dependencies() +load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains") + +rules_proto_dependencies() + +rules_proto_toolchains() + load("@io_bazel_rules_webtesting//web:repositories.bzl", "web_test_repositories") web_test_repositories() diff --git a/examples/protocol_buffers/app.ts b/examples/protocol_buffers/app.ts index 2308f5e69b..044b20261a 100644 --- a/examples/protocol_buffers/app.ts +++ b/examples/protocol_buffers/app.ts @@ -1,8 +1,9 @@ -import {Proto} from './car'; +import {Car} from 'examples_protocol_buffers/car_pb'; + +const car = new Car(); +car.setMake('Porsche'); -const serverResponse = `{"make": "Porsche"}`; -const car = Proto.Car.create(JSON.parse(serverResponse)); const el: HTMLDivElement = document.createElement('div'); -el.innerText = `Car from server: ${car.make}`; +el.innerText = `Car from server: ${car.getMake()}`; el.className = 'ts1'; document.body.appendChild(el); diff --git a/examples/protocol_buffers/car.spec.ts b/examples/protocol_buffers/car.spec.ts index 65f90cd069..6618fc1aaf 100644 --- a/examples/protocol_buffers/car.spec.ts +++ b/examples/protocol_buffers/car.spec.ts @@ -1,34 +1,18 @@ -import {Proto} from './car'; -import Long = require('long'); +import {Car} from 'examples_protocol_buffers/car_pb'; +import {Tire} from 'examples_protocol_buffers/tire_pb'; describe('protocol buffers', () => { it('allows creation of an object described by proto', () => { - const pontiac = Proto.Car.create({ - make: 'pontiac', - frontTires: { - width: 225, - aspectRatio: 65, - construction: 'R', - diameter: 17, - }, - }); - expect(pontiac.make).toEqual('pontiac'); - if (!pontiac.frontTires) { - fail('Should have frontTires set'); - } else { - expect(pontiac.frontTires.width).toEqual(225); - } - }); + const tires = new Tire(); + tires.setAspectRatio(65); + tires.setWidth(225); + tires.setConstruction('R'); + tires.setDiameter(17); + + const pontiac = new Car(); + pontiac.setMake('pontiac'); + pontiac.setFrontTires(tires) - // Asserts that longs are handled correctly. - // This value comes from https://github.com/dcodeIO/long.js#background - it('handles long values correctly', () => { - const pontiac = Proto.Car.create({ - make: 'pontiac', - // Long.MAX_VALUE - mileage: new Long(0xFFFFFFFF, 0x7FFFFFFF), - }); - const object = Proto.Car.toObject(pontiac, {longs: String}); - expect(object['mileage']).toEqual('9223372036854775807'); + expect(pontiac.getMake()).toEqual('pontiac'); }); }); diff --git a/examples/protocol_buffers/index.html b/examples/protocol_buffers/index.html index 6f8690ee3e..e3c98719b7 100644 --- a/examples/protocol_buffers/index.html +++ b/examples/protocol_buffers/index.html @@ -3,8 +3,6 @@ protocol_buffers example - - \ No newline at end of file diff --git a/examples/protocol_buffers/package.json b/examples/protocol_buffers/package.json index a2d4500890..4bac109e76 100644 --- a/examples/protocol_buffers/package.json +++ b/examples/protocol_buffers/package.json @@ -9,6 +9,8 @@ "@types/jasmine": "2.8.2", "@types/long": "^4.0.0", "@types/node": "11.11.1", + "google-protobuf": "3.11.4", + "grpc-web": "1.0.7", "http-server": "^0.11.1", "karma": "~4.1.0", "karma-chrome-launcher": "2.2.0", @@ -21,6 +23,8 @@ "protractor": "^5.4.2", "requirejs": "2.3.6", "rollup": "1.20.3", + "rollup-plugin-commonjs": "10.1.0", + "rollup-plugin-node-resolve": "5.2.0", "terser": "4.3.1", "typescript": "^3.3.1" }, diff --git a/examples/protocol_buffers/rollup.config.js b/examples/protocol_buffers/rollup.config.js index c7bad73937..acd72720ea 100644 --- a/examples/protocol_buffers/rollup.config.js +++ b/examples/protocol_buffers/rollup.config.js @@ -1,8 +1,9 @@ +const commonjs = require('rollup-plugin-commonjs'); +const nodeRequire = require('rollup-plugin-node-resolve'); + module.exports = { - // indicate which modules should be treated as external - external: [ - 'long', - 'protobufjs/minimal', + plugins: [ + nodeRequire(), + commonjs(), ], - output: {globals: {long: 'Long', 'protobufjs/minimal': 'protobuf'}} -}; +}; \ No newline at end of file diff --git a/examples/protocol_buffers/yarn.lock b/examples/protocol_buffers/yarn.lock index 8902e36a36..fdc585a3dd 100644 --- a/examples/protocol_buffers/yarn.lock +++ b/examples/protocol_buffers/yarn.lock @@ -107,6 +107,11 @@ resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.0.tgz#719551d2352d301ac8b81db732acb6bdc28dbdef" integrity sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q== +"@types/node@*": + version "13.9.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.0.tgz#5b6ee7a77faacddd7de719017d0bc12f52f81589" + integrity sha512-0ARSQootUG1RljH2HncpsY2TJBfGQIKOOi7kxzUY6z54ePu/ZD+wJA8zI2Q6v8rol2qpG/rvqsReco8zNMPvhQ== + "@types/node@11.11.1": version "11.11.1" resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.1.tgz#9ee55ffce20f72e141863b0036a6e51c6fc09a1f" @@ -127,6 +132,13 @@ resolved "https://registry.yarnpkg.com/@types/q/-/q-0.0.32.tgz#bd284e57c84f1325da702babfc82a5328190c0c5" integrity sha1-vShOV8hPEyXacCur/IKlMoGQwMU= +"@types/resolve@0.0.8": + version "0.0.8" + resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194" + integrity sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ== + dependencies: + "@types/node" "*" + "@types/selenium-webdriver@^3.0.0": version "3.0.16" resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-3.0.16.tgz#50a4755f8e33edacd9c406729e9b930d2451902a" @@ -461,6 +473,11 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== +builtin-modules@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484" + integrity sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw== + bytebuffer@~5: version "5.0.1" resolved "https://registry.yarnpkg.com/bytebuffer/-/bytebuffer-5.0.1.tgz#582eea4b1a873b6d020a48d58df85f0bba6cfddd" @@ -924,6 +941,11 @@ escape-string-regexp@^1.0.2: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= +estree-walker@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362" + integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w== + eventemitter3@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.0.tgz#d65176163887ee59f386d64c82610b696a4a74eb" @@ -1161,11 +1183,21 @@ globby@^5.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" +google-protobuf@3.11.4: + version "3.11.4" + resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.11.4.tgz#598ca405a3cfa917a2132994d008b5932ef42014" + integrity sha512-lL6b04rDirurUBOgsY2+LalI6Evq8eH5TcNzi7TYQ3BsIWelT0KSOQSBsXuavEkNf+odQU6c0lgz3UsZXeNX9Q== + graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6: version "4.2.2" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.2.tgz#6f0952605d0140c1cfdb138ed005775b92d67b02" integrity sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q== +grpc-web@1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/grpc-web/-/grpc-web-1.0.7.tgz#9e4fbcf63d3734515332ab59e42baa7d0d290015" + integrity sha512-Fkbz1nyvvt6GC6ODcxh9Fen6LLB3OTCgGHzHwM2Eni44SUhzqPz1UQgFp9sfBEfInOhx3yBdwo9ZLjZAmJ+TtA== + har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -1465,6 +1497,11 @@ is-glob@^4.0.0: dependencies: is-extglob "^2.1.1" +is-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" + integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= + is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" @@ -1498,6 +1535,13 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" +is-reference@^1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.1.4.tgz#3f95849886ddb70256a3e6d062b1a68c13c51427" + integrity sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw== + dependencies: + "@types/estree" "0.0.39" + is-regex@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" @@ -1769,6 +1813,13 @@ lru-cache@4.1.x: pseudomap "^1.0.2" yallist "^2.1.2" +magic-string@^0.25.2: + version "0.25.7" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" + integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== + dependencies: + sourcemap-codec "^1.4.4" + map-cache@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" @@ -2151,6 +2202,11 @@ path-is-inside@^1.0.1: resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" @@ -2403,6 +2459,13 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= +resolve@^1.11.0, resolve@^1.11.1: + version "1.15.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" + integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== + dependencies: + path-parse "^1.0.6" + ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" @@ -2420,6 +2483,35 @@ rimraf@^2.2.8, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimra dependencies: glob "^7.1.3" +rollup-plugin-commonjs@10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz#417af3b54503878e084d127adf4d1caf8beb86fb" + integrity sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q== + dependencies: + estree-walker "^0.6.1" + is-reference "^1.1.2" + magic-string "^0.25.2" + resolve "^1.11.0" + rollup-pluginutils "^2.8.1" + +rollup-plugin-node-resolve@5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-5.2.0.tgz#730f93d10ed202473b1fb54a5997a7db8c6d8523" + integrity sha512-jUlyaDXts7TW2CqQ4GaO5VJ4PwwaV8VUGA7+km3n6k6xtOEacf61u0VXwN80phY/evMcaS+9eIeJ9MOyDxt5Zw== + dependencies: + "@types/resolve" "0.0.8" + builtin-modules "^3.1.0" + is-module "^1.0.0" + resolve "^1.11.1" + rollup-pluginutils "^2.8.1" + +rollup-pluginutils@^2.8.1: + version "2.8.2" + resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e" + integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ== + dependencies: + estree-walker "^0.6.1" + rollup@1.20.3: version "1.20.3" resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.20.3.tgz#6243f6c118ca05f56b2d9433112400cd834a1eb8" @@ -2638,6 +2730,11 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +sourcemap-codec@^1.4.4: + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" diff --git a/package.json b/package.json index d426412837..13a4d45885 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,8 @@ "conventional-changelog-cli": "^2.0.21", "core-util-is": "^1.0.2", "date-fns": "1.30.1", + "google-protobuf": "^3.6.1", + "grpc-web": "^1.0.7", "hello": "file:./tools/npm_packages/hello", "history-server": "^1.3.1", "http-server": "^0.11.1", diff --git a/packages/labs/src/BUILD.bazel b/packages/labs/src/BUILD.bazel index 8a75244100..25bafdd0f7 100644 --- a/packages/labs/src/BUILD.bazel +++ b/packages/labs/src/BUILD.bazel @@ -17,14 +17,13 @@ # The generated `@bazel/labs` npm package contains a trimmed BUILD file using INTERNAL fences. package(default_visibility = ["//visibility:public"]) -exports_files(["tsconfig.json"]) - filegroup( name = "package_contents", srcs = glob(["*.bzl"]) + [ "BUILD.bazel", "README.md", "package.json", + "//grpc_web:package_contents", "//protobufjs:package_contents", ], ) diff --git a/packages/labs/src/grpc_web/BUILD.bazel b/packages/labs/src/grpc_web/BUILD.bazel new file mode 100644 index 0000000000..e6aca6d0ca --- /dev/null +++ b/packages/labs/src/grpc_web/BUILD.bazel @@ -0,0 +1,32 @@ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") +load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary") +load("@build_bazel_rules_nodejs//internal/js_library:js_library.bzl", "js_library") + +package(default_visibility = ["//visibility:public"]) + +bzl_library( + name = "bzl", + srcs = glob(["*.bzl"]), + visibility = ["//:__pkg__"], +) + +nodejs_binary( + name = "change_import_style", + entry_point = ":change_import_style.js", + node_modules = "@build_bazel_rules_typescript_grpc_web_compiletime_deps//:node_modules", + visibility = ["//visibility:public"], +) + +js_library( + name = "bootstrap_scripts", + srcs = [ + "@npm//google-protobuf:google-protobuf__umd", + "@npm//grpc-web:grpc-web__umd", + ], +) + +filegroup( + name = "package_contents", + srcs = glob(["*"]), + visibility = ["//:__pkg__"], +) diff --git a/packages/labs/src/grpc_web/README.md b/packages/labs/src/grpc_web/README.md new file mode 100644 index 0000000000..e5dc36762f --- /dev/null +++ b/packages/labs/src/grpc_web/README.md @@ -0,0 +1,82 @@ +# ts_proto_library + +Bazel rule for generating TypeScript declarations for JavaScript protocol buffers +and GRPC Web service definitions using the [grpc-web](https://github.com/grpc/grpc-web) +protoc plugin. + +## Getting Started + +Before you can use `ts_proto_library`, you must first setup: + +- [rules_proto](https://github.com/bazelbuild/rules_proto) +- [rules_nodejs](https://github.com/bazelbuild/rules_nodejs) + +Once those are setup, add the following to your workspace: + +```python +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +# TODO: Setup rules_proto +# TODO: Setup rules_nodejs + +load("@npm_bazel_labs//:package.bzl", "npm_bazel_labs_dependencies") + +npm_bazel_labs_dependencies() +``` + +Then, in your `BUILD` file: + +```python +load("@rules_proto//:index.bzl", "typescript_proto_library") +load("@npm_bazel_labs//:index.bzl", "ts_proto_library") + +proto_library( + name = "test_proto", + srcs = [ + "test.proto", + ], +) + +ts_proto_library( + name = "test_ts_proto", + proto = ":test_proto", +) +``` + +You can now use the `test_ts_proto` target as a `dep` in other `ts_library` targets. However, you will need to include the following dependencies at runtime yourself: + +- `google-protobuf` +- `grpc-web` + +UMD versions of these runtime dependencies are provided by `@npm_bazel_labs//grpc_web:bootstrap_scripts` (for use within `ts_devserver` and `karma_web_test_suite`) + +See `//examples/protocol_buffers/BUILD.bazel` for an example. + +## IDE Code Completion + +To get code completion working for the generated protos in your IDE, add the following to your +`tsconfig.json`: + +```js +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + // Replace with the name of your workspace + "/*": [ + "*", // Enables absolute paths for src files in your project + "bazel-bin/*" // Enables referencing generate protos with absolute paths + ] + } + } +} +``` + +## Implementation Details +A bazel aspect is used to generate `ts_library` compatible output for all transitive +dependencies of the proto passed to `ts_proto_library`. + +In its current state (as of March 15, 2020) the `grpc` protoc plugin is not fully capable of +producing typescript source files. It is however, capable of generating type declarations and +`commonjs` implementations. To make these output's compatible with `ts_library` the generated +`commonjs` output is transformed into both ES5 UMD module files and ES6 module files. diff --git a/packages/labs/src/grpc_web/change_import_style.js b/packages/labs/src/grpc_web/change_import_style.js new file mode 100644 index 0000000000..f1a285d93e --- /dev/null +++ b/packages/labs/src/grpc_web/change_import_style.js @@ -0,0 +1,151 @@ +/** + * Converts a list of generated protobuf-js files from commonjs modules into named AMD modules. + * + * Initial implementation derived from + * https://github.com/Dig-Doug/rules_typescript_proto/blob/master/src/change_import_style.ts + * + * Arguments: + * --workspace_name + * --input_base_path + * --output_module_name + * --input_file_path + * --output_file_path + */ +const minimist = require('minimist'); +const fs = require('fs'); + +function main() { + const args = minimist(process.argv.slice(2)); + + /** + * Proto files with RPC service definitions will produce an extra file. During a bazel aspect we + * have to declare our outputs without knowing the contents of the proto file so we generate an + * empty stub for files without service definitions. + */ + if (!fs.existsSync(args.input_file_path)) { + fs.writeFileSync(args.input_file_path, '', 'utf8'); + fs.writeFileSync(args.input_file_path.replace('.js', '.d.ts'), '', 'utf8'); + fs.writeFileSync(args.output_umd_path, '', 'utf8'); + fs.writeFileSync(args.output_es6_path, '', 'utf8'); + } + + const initialContents = fs.readFileSync(args.input_file_path, 'utf8'); + + const umdContents = convertToUmd(args, initialContents); + fs.writeFileSync(args.output_umd_path, umdContents, 'utf8'); + + const commonJsContents = convertToESM(args, initialContents); + fs.writeFileSync(args.output_es6_path, commonJsContents, 'utf8'); +} + +function replaceRecursiveFilePaths(args) { + return (contents) => { + return contents.replace(/(\.\.\/)+/g, `${args.workspace_name}/`); + }; +} + +function removeJsExtensionsFromRequires(contents) { + return contents.replace(/(require\(.*).js/g, (_, captureGroup) => { + return captureGroup; + }); +} + +function convertToUmd(args, initialContents) { + const wrapInAMDModule = (contents) => { + return `// GENERATED CODE DO NOT EDIT + (function (factory) { + if (typeof module === "object" && typeof module.exports === "object") { + var v = factory(require, exports); + if (v !== undefined) module.exports = v; + } + else if (typeof define === "function" && define.amd) { + define("${args.input_base_path}/${args.output_module_name}", factory); + } + })(function (require, exports) { + ${contents.replace(/module.exports =/g, 'return')} + }); +`; + }; + + const transformations = + [wrapInAMDModule, replaceRecursiveFilePaths(args), removeJsExtensionsFromRequires]; + return transformations.reduce((currentContents, transform) => { + return transform(currentContents); + }, initialContents); +} + +// Converts the CommonJS format from protoc to the ECMAScript Module format. +// Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules +function convertToESM(args, initialContents) { + const replaceGoogExtendWithExports = (contents) => { + return contents.replace(/goog\.object\.extend\(exports, ([\w\.]+)\);/g, (_, packageName) => { + const exportSymbols = /goog\.exportSymbol\('([\w\.]+)',.*\);/g; + const symbols = []; + + let match; + while ((match = exportSymbols.exec(initialContents))) { + // We want to ignore embedded export targets, IE: + // `DeliveryPerson.DataCase`. + const exportTarget = match[1].substr(packageName.length + 1); + if (!exportTarget.includes('.')) { + symbols.push(exportTarget); + } + } + + return `export const { ${symbols.join(', ')} } = ${packageName}`; + }); + }; + + const replaceCMDefaultExportWithExports = (contents) => { + return contents.replace(/module.exports = ([\w\.]+)\;/g, (_, packageName) => { + const exportSymbols = new RegExp(`${packageName.replace('.', '\\.')}\.([\\w\\.]+) =`, 'g'); + + const symbols = []; + + let match; + while ((match = exportSymbols.exec(initialContents))) { + // We want to ignore embedded export targets, IE: + // `DeliveryPerson.DataCase`. + const exportTarget = match[1]; + if (!exportTarget.includes('.')) { + symbols.push(exportTarget); + } + } + + return `export const { ${symbols.join(', ')} } = ${packageName};`; + }); + }; + + const replaceRequiresWithImports = (contents) => { + return contents + .replace( + /var ([\w\d_]+) = require\((['"][\.\\]*[\w\d@/_-]+['"])\)/g, 'import * as $1 from $2') + .replace( + /([\.\w\d_]+) = require\((['"][\.\w\d@/_-]+['"])\)/g, (_, variable, importPath) => { + const normalizedVariable = variable.replace(/\./g, '_'); + return `import * as ${normalizedVariable} from ${importPath};\n${variable} = {...${ + normalizedVariable}}`; + }); + }; + + const replaceRequiresWithSubpackageImports = (contents) => { + return contents.replace( + /var ([\w\d_]+) = require\((['"][\w\d@/_-]+['"])\)\.([\w\d_]+);/g, + 'import * as $1 from $2;'); + }; + + const replaceCJSExportsWithECMAExports = (contents) => { + return contents.replace(/exports\.([\w\d_]+) = .*;/g, 'export { $1 };'); + }; + + const transformations = [ + replaceRecursiveFilePaths(args), removeJsExtensionsFromRequires, replaceGoogExtendWithExports, + replaceRequiresWithImports, replaceRequiresWithSubpackageImports, + replaceCMDefaultExportWithExports, replaceCJSExportsWithECMAExports + ]; + return transformations.reduce((currentContents, transform) => { + return transform(currentContents); + }, initialContents); +} + +main(); diff --git a/packages/labs/src/grpc_web/package.json b/packages/labs/src/grpc_web/package.json new file mode 100644 index 0000000000..a94543cf4c --- /dev/null +++ b/packages/labs/src/grpc_web/package.json @@ -0,0 +1,9 @@ +{ + "name": "grpc_web", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "minimist": "1.2.0" + } + } + \ No newline at end of file diff --git a/packages/labs/src/grpc_web/ts_proto_library.bzl b/packages/labs/src/grpc_web/ts_proto_library.bzl new file mode 100644 index 0000000000..bec8677627 --- /dev/null +++ b/packages/labs/src/grpc_web/ts_proto_library.bzl @@ -0,0 +1,283 @@ +# Copyright 2020 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"Protocol Buffers" + +load("@build_bazel_rules_nodejs//:providers.bzl", "DeclarationInfo", "JSEcmaScriptModuleInfo", "JSNamedModuleInfo") +load("@rules_proto//proto:defs.bzl", "ProtoInfo") + +typescript_proto_library_aspect = provider( + fields = { + "deps_dts": "The transitive dependencies' TS definitions", + "deps_es5": "The transitive ES5 JS dependencies", + "deps_es6": "The transitive ES6 JS dependencies", + "dts_outputs": "Ths TS definition files produced directly from the src protos", + "es5_outputs": "The ES5 JS files produced directly from the src protos", + "es6_outputs": "The ES6 JS files produced directly from the src protos", + }, +) + +# TODO(dan): Replace with |proto_common.direct_source_infos| when +# https://github.com/bazelbuild/rules_proto/pull/22 lands. +# Derived from https://github.com/grpc-ecosystem/grpc-gateway/blob/e8db07a3923d3f5c77dbcea96656afe43a2757a8/protoc-gen-swagger/defs.bzl#L11 +def _direct_source_infos(proto_info, provided_sources = []): + """Returns sequence of `ProtoFileInfo` for `proto_info`'s direct sources. + Files that are both in `proto_info`'s direct sources and in + `provided_sources` are skipped. This is useful, e.g., for well-known + protos that are already provided by the Protobuf runtime. + Args: + proto_info: An instance of `ProtoInfo`. + provided_sources: Optional. A sequence of files to ignore. + Usually, these files are already provided by the + Protocol Buffer runtime (e.g. Well-Known protos). + Returns: A sequence of `ProtoFileInfo` containing information about + `proto_info`'s direct sources. + """ + + source_root = proto_info.proto_source_root + if "." == source_root: + return [struct(file = src, import_path = src.path) for src in proto_info.direct_sources] + + offset = len(source_root) + 1 # + '/'. + + infos = [] + for src in proto_info.direct_sources: + infos.append(struct(file = src, import_path = src.path[offset:])) + + return infos + +def _get_protoc_inputs(target, ctx): + inputs = [] + inputs += target[ProtoInfo].direct_sources + inputs += target[ProtoInfo].transitive_descriptor_sets.to_list() + return inputs + +def _build_protoc_command(target, ctx): + protoc_command = "%s" % (ctx.executable._protoc.path) + + protoc_command += " --plugin=protoc-gen-grpc-web=%s" % (ctx.executable._protoc_gen_grpc_web.path) + + protoc_output_dir = ctx.var["BINDIR"] + protoc_command += " --grpc-web_out=import_style=commonjs+dts,mode=grpcweb:%s" % (protoc_output_dir) + protoc_command += " --js_out=import_style=commonjs:%s" % (protoc_output_dir) + + descriptor_sets_paths = [desc.path for desc in target[ProtoInfo].transitive_descriptor_sets.to_list()] + + pathsep = ctx.configuration.host_path_separator + protoc_command += " --descriptor_set_in=\"%s\"" % (pathsep.join(descriptor_sets_paths)) + + proto_file_infos = _direct_source_infos(target[ProtoInfo]) + for f in proto_file_infos: + protoc_command += " %s" % f.import_path + + return protoc_command + +def _create_post_process_command(target, ctx, js_outputs, js_outputs_es6): + """ + Builds a post-processing command that: + - Updates the existing protoc output files to be UMD modules + - Creates a new es6 file from the original protoc output + """ + convert_commands = [] + for [output, output_es6] in zip(js_outputs, js_outputs_es6): + file_path = "/".join([p for p in [ + ctx.workspace_name, + ctx.label.package, + ] if p]) + file_name = output.basename[:-len(output.extension) - 1] + + convert_command = ctx.executable._change_import_style.path + convert_command += " --workspace_name={}".format(ctx.workspace_name) + convert_command += " --input_base_path={}".format(file_path) + convert_command += " --output_module_name={}".format(file_name) + convert_command += " --input_file_path={}".format(output.path) + convert_command += " --output_umd_path={}".format(output.path) + convert_command += " --output_es6_path={}".format(output_es6.path) + convert_commands.append(convert_command) + + return " && ".join(convert_commands) + +def _get_outputs(target, ctx): + """ + Calculates all of the files that will be generated by the aspect. + """ + js_outputs = [] + js_outputs_es6 = [] + dts_outputs = [] + for src in target[ProtoInfo].direct_sources: + file_name = src.basename[:-len(src.extension) - 1] + generated_files = ["_pb", "_grpc_web_pb"] + for f in generated_files: + full_name = file_name + f + output = ctx.actions.declare_file(full_name + ".js") + js_outputs.append(output) + output_es6 = ctx.actions.declare_file(full_name + ".mjs") + js_outputs_es6.append(output_es6) + output_d_ts = ctx.actions.declare_file(file_name + f + ".d.ts") + dts_outputs.append(output_d_ts) + + return [js_outputs, js_outputs_es6, dts_outputs] + +def ts_proto_library_aspect_(target, ctx): + """ + A bazel aspect that is applied on every proto_library rule on the transitive set of dependencies + of a ts_proto_library rule. + + Handles running protoc to produce the generated JS and TS files. + """ + + [js_outputs, js_outputs_es6, dts_outputs] = _get_outputs(target, ctx) + protoc_outputs = dts_outputs + js_outputs + js_outputs_es6 + + all_commands = [ + _build_protoc_command(target, ctx), + _create_post_process_command(target, ctx, js_outputs, js_outputs_es6), + ] + + tools = [] + tools.extend(ctx.files._protoc) + tools.extend(ctx.files._protoc_gen_grpc_web) + tools.extend(ctx.files._change_import_style) + + ctx.actions.run_shell( + inputs = depset( + direct = _get_protoc_inputs(target, ctx), + transitive = [depset(ctx.files._well_known_protos)], + ), + outputs = protoc_outputs, + progress_message = "Creating Typescript pb files %s" % ctx.label, + command = " && ".join(all_commands), + tools = depset(tools), + ) + + dts_outputs = depset(dts_outputs) + es5_outputs = depset(js_outputs) + es6_outputs = depset(js_outputs_es6) + deps_dts = [] + deps_es5 = [] + deps_es6 = [] + + for dep in ctx.rule.attr.deps: + aspect_data = dep[typescript_proto_library_aspect] + deps_dts.append(aspect_data.dts_outputs) + deps_dts.append(aspect_data.deps_dts) + deps_es5.append(aspect_data.es5_outputs) + deps_es5.append(aspect_data.deps_es5) + deps_es6.append(aspect_data.es6_outputs) + deps_es6.append(aspect_data.deps_es6) + + return [typescript_proto_library_aspect( + dts_outputs = dts_outputs, + es5_outputs = es5_outputs, + es6_outputs = es6_outputs, + deps_dts = depset(transitive = deps_dts), + deps_es5 = depset(transitive = deps_es5), + deps_es6 = depset(transitive = deps_es6), + )] + +ts_proto_library_aspect = aspect( + implementation = ts_proto_library_aspect_, + attr_aspects = ["deps"], + attrs = { + "_change_import_style": attr.label( + executable = True, + cfg = "host", + allow_files = True, + default = Label("@npm_bazel_labs//grpc_web:change_import_style"), + ), + "_protoc": attr.label( + allow_single_file = True, + executable = True, + cfg = "host", + default = Label("@com_google_protobuf//:protoc"), + ), + "_protoc_gen_grpc_web": attr.label( + allow_files = True, + executable = True, + cfg = "host", + default = Label("@com_github_grpc_grpc_web//javascript/net/grpc/web:protoc-gen-grpc-web"), + ), + "_well_known_protos": attr.label( + default = "@com_google_protobuf//:well_known_protos", + allow_files = True, + ), + }, +) + +def _ts_proto_library_impl(ctx): + """ + Handles converting the aspect output into a provider compatible with the rules_typescript rules. + """ + aspect_data = ctx.attr.proto[typescript_proto_library_aspect] + dts_outputs = aspect_data.dts_outputs + transitive_declarations = depset(transitive = [dts_outputs, aspect_data.deps_dts]) + es5_outputs = aspect_data.es5_outputs + es6_outputs = aspect_data.es6_outputs + outputs = depset(transitive = [es5_outputs, es6_outputs, dts_outputs]) + + es5_srcs = depset(transitive = [es5_outputs, aspect_data.deps_es5]) + es6_srcs = depset(transitive = [es6_outputs, aspect_data.deps_es6]) + return struct( + typescript = struct( + declarations = dts_outputs, + transitive_declarations = transitive_declarations, + es5_sources = es5_srcs, + es6_sources = es6_srcs, + transitive_es5_sources = es5_srcs, + transitive_es6_sources = es6_srcs, + ), + providers = [ + DefaultInfo(files = outputs), + DeclarationInfo( + declarations = dts_outputs, + transitive_declarations = transitive_declarations, + type_blacklisted_declarations = depset([]), + ), + JSNamedModuleInfo( + direct_sources = es5_srcs, + sources = es5_srcs, + ), + JSEcmaScriptModuleInfo( + direct_sources = es6_srcs, + sources = es6_srcs, + ), + ], + ) + +ts_proto_library = rule( + attrs = { + "proto": attr.label( + allow_single_file = True, + aspects = [ts_proto_library_aspect], + mandatory = True, + providers = [ProtoInfo], + ), + "_protoc": attr.label( + allow_single_file = True, + cfg = "host", + default = Label("@com_google_protobuf//:protoc"), + executable = True, + ), + "_protoc_gen_grpc_web": attr.label( + allow_files = True, + cfg = "host", + default = Label("@com_github_grpc_grpc_web//javascript/net/grpc/web:protoc-gen-grpc-web"), + executable = True, + ), + "_well_known_protos": attr.label( + allow_files = True, + default = "@com_google_protobuf//:well_known_protos", + ), + }, + implementation = _ts_proto_library_impl, +) diff --git a/packages/labs/src/grpc_web/yarn.lock b/packages/labs/src/grpc_web/yarn.lock new file mode 100644 index 0000000000..f312ae1221 --- /dev/null +++ b/packages/labs/src/grpc_web/yarn.lock @@ -0,0 +1,8 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +minimist@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= diff --git a/packages/labs/src/index.bzl b/packages/labs/src/index.bzl index a3cc31de2f..4e108cdb4d 100644 --- a/packages/labs/src/index.bzl +++ b/packages/labs/src/index.bzl @@ -15,6 +15,8 @@ """Public API surface is re-exported here. """ -load("//protobufjs:ts_proto_library.bzl", _ts_proto_library = "ts_proto_library") +load("@npm_bazel_labs//grpc_web:ts_proto_library.bzl", _ts_proto_library = "ts_proto_library") +load("@npm_bazel_labs//protobufjs:ts_proto_library.bzl", _protobufjs_ts_library = "ts_proto_library") ts_proto_library = _ts_proto_library +protobufjs_ts_library = _protobufjs_ts_library diff --git a/packages/labs/src/mock_io_bazel_rules_closure.bzl b/packages/labs/src/mock_io_bazel_rules_closure.bzl new file mode 100644 index 0000000000..ce4e04bcc6 --- /dev/null +++ b/packages/labs/src/mock_io_bazel_rules_closure.bzl @@ -0,0 +1,28 @@ +"Mock transitive toolchain dependencies that are uneeded" + +def mock_io_bazel_rules_closure_impl(repository_ctx): + repository_ctx.file( + "closure/BUILD.bazel", + content = """\ +package(default_visibility = ["//visibility:public"]) + +exports_files(["defs.bzl"]) +""", + ) + repository_ctx.file( + "closure/defs.bzl", + content = """\ +"Minimal implementation to make loading phase succeed" + +def noop(**kwargs): + pass + +closure_js_binary = noop +closure_js_library = noop +closure_js_test = noop +""", + ) + +mock_io_bazel_rules_closure = repository_rule( + mock_io_bazel_rules_closure_impl, +) diff --git a/packages/labs/src/package.bzl b/packages/labs/src/package.bzl index 636bc63ef7..bcb71d446b 100644 --- a/packages/labs/src/package.bzl +++ b/packages/labs/src/package.bzl @@ -1,8 +1,62 @@ "Install toolchain dependencies" +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") load("@build_bazel_rules_nodejs//:index.bzl", "yarn_install") +load(":mock_io_bazel_rules_closure.bzl", "mock_io_bazel_rules_closure") def npm_bazel_labs_dependencies(): + """ + Fetch our transitive dependencies. + + If the user wants to get a different version of these, they can just fetch it + from their WORKSPACE before calling this function, or not call this function at all. + """ + + _maybe( + http_archive, + name = "com_github_grpc_grpc_web", + sha256 = "04460e28ffa80bfc797a8758da10ba40107347ef0af8e9cc065ade10398da4bb", + strip_prefix = "grpc-web-1.0.7", + urls = [ + "https://github.com/grpc/grpc-web/archive/1.0.7.tar.gz", + ], + ) + + _maybe( + http_archive, + name = "com_github_grpc_grpc_web", + sha256 = "04460e28ffa80bfc797a8758da10ba40107347ef0af8e9cc065ade10398da4bb", + strip_prefix = "grpc-web-1.0.7", + urls = [ + "https://github.com/grpc/grpc-web/archive/1.0.7.tar.gz", + ], + ) + + _maybe( + http_archive, + name = "rules_proto", + sha256 = "4d421d51f9ecfe9bf96ab23b55c6f2b809cbaf0eea24952683e397decfbd0dd0", + strip_prefix = "rules_proto-f6b8d89b90a7956f6782a4a3609b2f0eee3ce965", + urls = [ + "https://github.com/bazelbuild/rules_proto/archive/f6b8d89b90a7956f6782a4a3609b2f0eee3ce965.tar.gz", + ], + ) + + _maybe( + mock_io_bazel_rules_closure, + name = "io_bazel_rules_closure", + ) + + yarn_install( + name = "build_bazel_rules_typescript_grpc_web_compiletime_deps", + package_json = "@npm_bazel_labs//grpc_web:package.json", + yarn_lock = "@npm_bazel_labs//grpc_web:yarn.lock", + # Do not symlink node_modules as when used in downstream repos we should not create + # node_modules folders in the @npm_bazel_typescript external repository. This is + # not supported by managed_directories. + symlink_node_modules = False, + ) + yarn_install( name = "build_bazel_rules_typescript_protobufs_compiletime_deps", package_json = "@npm_bazel_labs//protobufjs:package.json", @@ -12,3 +66,7 @@ def npm_bazel_labs_dependencies(): # not supported by managed_directories. symlink_node_modules = False, ) + +def _maybe(repo_rule, name, **kwargs): + if name not in native.existing_rules(): + repo_rule(name = name, **kwargs) diff --git a/packages/labs/src/tsconfig.json b/packages/labs/src/tsconfig.json deleted file mode 100644 index 8dc142972f..0000000000 --- a/packages/labs/src/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "compilerOptions": { - "strict": true - } -} diff --git a/packages/labs/test/grpc_web/BUILD.bazel b/packages/labs/test/grpc_web/BUILD.bazel new file mode 100644 index 0000000000..204bca20f1 --- /dev/null +++ b/packages/labs/test/grpc_web/BUILD.bazel @@ -0,0 +1,116 @@ +load("@npm_bazel_jasmine//:index.from_src.bzl", "jasmine_node_test") +load("@npm_bazel_karma//:index.from_src.bzl", "karma_web_test_suite") +load("@npm_bazel_rollup//:index.from_src.bzl", "rollup_bundle") +load("@npm_bazel_typescript//:index.from_src.bzl", "ts_library") + +# This test checks that the protos can be resolved in a nodejs environment +jasmine_node_test( + name = "commonjs_test", + data = [ + "@npm//google-protobuf", + "@npm//grpc-web", + ], + deps = [ + ":commonjs_test_lib", + ], +) + +ts_library( + name = "commonjs_test_lib", + srcs = [ + "commonjs_test.spec.ts", + ], + deps = [ + "//packages/labs/test/grpc_web/proto:pizza_service_ts_proto", + "//packages/labs/test/grpc_web/proto/common:delivery_person_ts_proto", + "@npm//@types/jasmine", + ], +) + +ts_library( + name = "proto_with_deps_test", + testonly = 1, + srcs = ["proto_with_deps_test.spec.ts"], + deps = [ + "//packages/labs/test/grpc_web/proto/common:delivery_person_ts_proto", + "@npm//@types/jasmine", + # Don't put pizza_ts_proto here, we want to test it is included transitively + ], +) + +karma_web_test_suite( + name = "proto_with_deps_test_suite", + srcs = [ + "@npm_bazel_labs//grpc_web:bootstrap_scripts", + ], + browsers = [ + "@io_bazel_rules_webtesting//browsers:chromium-local", + ], + tags = ["native"], + deps = [ + ":proto_with_deps_test", + ], +) + +ts_library( + name = "pizza_service_proto_test", + testonly = 1, + srcs = ["pizza_service_proto_test.spec.ts"], + deps = [ + "//packages/labs/test/grpc_web/proto/common:pizza_ts_proto", + "@npm//@types/jasmine", + "//packages/labs/test/grpc_web/proto:pizza_service_ts_proto", + # Don't put delivery_person_ts_proto here, we want to test it is included transitively + ], +) + +karma_web_test_suite( + name = "pizza_service_proto_test_suite", + srcs = [ + "@npm_bazel_labs//grpc_web:bootstrap_scripts", + ], + browsers = [ + "@io_bazel_rules_webtesting//browsers:chromium-local", + ], + tags = ["native"], + deps = [ + ":pizza_service_proto_test", + ], +) + +rollup_bundle( + name = "test_es6_bundling", + config_file = "rollup.config.js", + entry_points = { + ":test_bundling.ts": "index", + }, + format = "cjs", + output_dir = True, + deps = [ + ":test_bundling_lib", + "@npm//rollup-plugin-commonjs", + "@npm//rollup-plugin-node-resolve", + ], +) + +ts_library( + name = "test_bundling_lib", + srcs = ["test_bundling.ts"], + deps = [ + "//packages/labs/test/grpc_web/proto:naming_styles_ts_proto", + "//packages/labs/test/grpc_web/proto/common:delivery_person_ts_proto", + "//packages/labs/test/grpc_web/proto/common:pizza_ts_proto", + "@npm//google-protobuf", + "@npm//grpc-web", + ], +) + +jasmine_node_test( + name = "rollup_test", + srcs = [ + ":rollup_test.spec.js", + ], + data = [ + ":test_es6_bundling", + ], +) diff --git a/packages/labs/test/grpc_web/commonjs_test.spec.ts b/packages/labs/test/grpc_web/commonjs_test.spec.ts new file mode 100644 index 0000000000..e49d23e71c --- /dev/null +++ b/packages/labs/test/grpc_web/commonjs_test.spec.ts @@ -0,0 +1,16 @@ +import deliveryPersonPb = require('build_bazel_rules_nodejs/packages/labs/test/grpc_web/proto/common/delivery_person_pb'); +import {PizzaServiceClient} from 'build_bazel_rules_nodejs/packages/labs/test/grpc_web/proto/pizza_service_grpc_web_pb'; + +describe('CommonJs', () => { + it('Loads imports using require()', () => { + expect(deliveryPersonPb).toBeDefined(); + + const person = new deliveryPersonPb.DeliveryPerson(); + person.setName('Doug'); + expect(person).toBeDefined(); + }); + + it('Loads imports using TS from syntax', () => { + expect(PizzaServiceClient).toBeDefined(); + }); +}); diff --git a/packages/labs/test/grpc_web/pizza_service_proto_test.spec.ts b/packages/labs/test/grpc_web/pizza_service_proto_test.spec.ts new file mode 100644 index 0000000000..9fa649a54f --- /dev/null +++ b/packages/labs/test/grpc_web/pizza_service_proto_test.spec.ts @@ -0,0 +1,41 @@ +import 'google-protobuf'; + +import {Pizza, PizzaSize} from 'build_bazel_rules_nodejs/packages/labs/test/grpc_web/proto/common/pizza_pb'; +import {PizzaServiceClient} from 'build_bazel_rules_nodejs/packages/labs/test/grpc_web/proto/pizza_service_grpc_web_pb'; +import {OrderPizzaRequest, OrderPizzaResponse} from 'build_bazel_rules_nodejs/packages/labs/test/grpc_web/proto/pizza_service_pb'; + +declare function require(module: string): any; + +describe('DeliveryPerson', () => { + it('Non-service imported classes should not be null', () => { + expect(OrderPizzaRequest).toBeDefined(); + expect(OrderPizzaResponse).toBeDefined(); + }); + + it('Service imported class should not be null', () => { + expect(PizzaServiceClient).toBeDefined(); + }); + + it('Generated code seems to work', () => { + const request = new OrderPizzaRequest(); + + const pizza = new Pizza(); + pizza.setSize(PizzaSize.PIZZA_SIZE_LARGE); + request.addPizzas(pizza); + + expect(request.getPizzasList().length).toBe(1); + }); + + it('delivery_person_ts_proto is included since it is a transitive dependency', () => { + const PROTOS = require( + 'build_bazel_rules_nodejs/packages/labs/test/grpc_web/proto/common/delivery_person_pb'); + const DeliveryPerson = PROTOS.DeliveryPerson; + const pizza = new Pizza(); + pizza.setSize(PizzaSize.PIZZA_SIZE_LARGE); + + const person = new DeliveryPerson(); + person.setPizzasList([pizza]); + + expect(person.getPizzasList().length).toBe(1); + }); +}); diff --git a/packages/labs/test/grpc_web/proto/BUILD.bazel b/packages/labs/test/grpc_web/proto/BUILD.bazel new file mode 100644 index 0000000000..ed53f63919 --- /dev/null +++ b/packages/labs/test/grpc_web/proto/BUILD.bazel @@ -0,0 +1,33 @@ +load("@rules_proto//proto:defs.bzl", "proto_library") + +package(default_visibility = ["//:__subpackages__"]) + +load("@npm_bazel_labs//:index.bzl", "ts_proto_library") + +proto_library( + name = "naming_styles_proto", + srcs = [ + "naming_styles.proto", + ], +) + +ts_proto_library( + name = "naming_styles_ts_proto", + proto = ":naming_styles_proto", +) + +proto_library( + name = "pizza_service_proto", + srcs = [ + "pizza_service.proto", + ], + deps = [ + "//packages/labs/test/grpc_web/proto/common:delivery_person_proto", + "//packages/labs/test/grpc_web/proto/common:pizza_proto", + ], +) + +ts_proto_library( + name = "pizza_service_ts_proto", + proto = ":pizza_service_proto", +) diff --git a/packages/labs/test/grpc_web/proto/common/BUILD.bazel b/packages/labs/test/grpc_web/proto/common/BUILD.bazel new file mode 100644 index 0000000000..cc4ac0d05d --- /dev/null +++ b/packages/labs/test/grpc_web/proto/common/BUILD.bazel @@ -0,0 +1,32 @@ +load("@rules_proto//proto:defs.bzl", "proto_library") + +package(default_visibility = ["//:__subpackages__"]) + +load("@npm_bazel_labs//:index.bzl", "ts_proto_library") + +proto_library( + name = "delivery_person_proto", + srcs = [ + "delivery_person.proto", + ], + deps = [ + ":pizza_proto", + ], +) + +ts_proto_library( + name = "delivery_person_ts_proto", + proto = ":delivery_person_proto", +) + +proto_library( + name = "pizza_proto", + srcs = [ + "pizza.proto", + ], +) + +ts_proto_library( + name = "pizza_ts_proto", + proto = ":pizza_proto", +) diff --git a/packages/labs/test/grpc_web/proto/common/delivery_person.proto b/packages/labs/test/grpc_web/proto/common/delivery_person.proto new file mode 100644 index 0000000000..667655c415 --- /dev/null +++ b/packages/labs/test/grpc_web/proto/common/delivery_person.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; + +package test.bazel.proto; + +import "packages/labs/test/grpc_web/proto/common/pizza.proto"; + +message DeliveryPerson { + // The name of the delivery person. + string name = 1; + + // The list of pizzas the delivery person has left to deliver. + repeated Pizza pizzas = 2; + + // Test nested structures inside protobufs. + oneof data { + string id = 3; + int64 id_v2 = 4; + } +} diff --git a/packages/labs/test/grpc_web/proto/common/pizza.proto b/packages/labs/test/grpc_web/proto/common/pizza.proto new file mode 100644 index 0000000000..48cf4ccb31 --- /dev/null +++ b/packages/labs/test/grpc_web/proto/common/pizza.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +package test.bazel.proto; + +enum PizzaSize { + PIZZA_SIZE_UNKNOWN = 0; + PIZZA_SIZE_SMALL = 1; + PIZZA_SIZE_MEDIUM = 2; + PIZZA_SIZE_LARGE = 3; +} + +message Pizza { + // The size of the pizza. + PizzaSize size = 1; + + // The list of toppings that are on the pizza. + repeated string topping_ids = 2; +} diff --git a/packages/labs/test/grpc_web/proto/naming_styles.proto b/packages/labs/test/grpc_web/proto/naming_styles.proto new file mode 100644 index 0000000000..3cb6948cbb --- /dev/null +++ b/packages/labs/test/grpc_web/proto/naming_styles.proto @@ -0,0 +1,60 @@ +// This file tests a bunch of valid naming schemes for messages. +syntax = "proto3"; + +package test.bazel.proto; + +message alllowercase { + int64 test = 1; +} + +message ALLUPPERCASE { + int64 test = 1; +} + +message lowerCamelCase { + int64 test = 1; +} + +message UpperCamelCase { + int64 test = 1; +} + +message snake_case_snake_case { + int64 test = 1; +} + +message Upper_snake_Case { + int64 test = 1; +} + +message M2M { + int64 test = 1; +} + +message M_2M { + int64 test = 1; +} + +message M2_M { + int64 test = 1; +} + +message M2M_ { + int64 test = 1; +} + +message m_22M { + int64 test = 1; +} + +message m42_M { + int64 test = 1; +} + +message m24M_ { + int64 test = 1; +} + +message M9 { + int64 test = 1; +} diff --git a/packages/labs/test/grpc_web/proto/pizza_service.proto b/packages/labs/test/grpc_web/proto/pizza_service.proto new file mode 100644 index 0000000000..0f5d68411c --- /dev/null +++ b/packages/labs/test/grpc_web/proto/pizza_service.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +package test.bazel.proto; + +import "packages/labs/test/grpc_web/proto/common/pizza.proto"; +import "packages/labs/test/grpc_web/proto/common/delivery_person.proto"; + +service PizzaService { + rpc OrderPizza(OrderPizzaRequest) returns (OrderPizzaResponse) { + } +} + +message OrderPizzaRequest { + // The list of pizzas to order. + repeated Pizza pizzas = 1; +} + +message OrderPizzaResponse { + // The person that will deliver the pizza. + DeliveryPerson delivery_person = 1; +} diff --git a/packages/labs/test/grpc_web/proto_with_deps_test.spec.ts b/packages/labs/test/grpc_web/proto_with_deps_test.spec.ts new file mode 100644 index 0000000000..cadf7f2390 --- /dev/null +++ b/packages/labs/test/grpc_web/proto_with_deps_test.spec.ts @@ -0,0 +1,32 @@ +import {DeliveryPerson} from 'build_bazel_rules_nodejs/packages/labs/test/grpc_web/proto/common/delivery_person_pb'; + +declare function require(module: string): any; + +describe('DeliveryPerson', () => { + it('Imported class should not be null', () => { + expect(DeliveryPerson).toBeDefined(); + }); + + it('Generated code seems to work', () => { + const person = new DeliveryPerson(); + const name = 'Doug'; + + person.setName(name); + + expect(person.getName()).toBe(name); + }); + + it('pizza_ts_proto is included since it is a transitive dependency', () => { + const PROTOS = + require('build_bazel_rules_nodejs/packages/labs/test/grpc_web/proto/common/pizza_pb'); + const Pizza = PROTOS.Pizza; + const PizzaSize = PROTOS.PizzaSize; + const person = new DeliveryPerson(); + + const pizza = new Pizza(); + pizza.setSize(PizzaSize.PIZZA_SIZE_LARGE); + person.setPizzasList([pizza]); + + expect(person.getPizzasList().length).toBe(1); + }); +}); diff --git a/packages/labs/test/grpc_web/rollup.config.js b/packages/labs/test/grpc_web/rollup.config.js new file mode 100644 index 0000000000..91086703b3 --- /dev/null +++ b/packages/labs/test/grpc_web/rollup.config.js @@ -0,0 +1,9 @@ +const commonjs = require('rollup-plugin-commonjs'); +const nodeRequire = require('rollup-plugin-node-resolve'); + +module.exports = { + plugins: [ + nodeRequire(), + commonjs(), + ], +}; diff --git a/packages/labs/test/grpc_web/rollup_test.spec.js b/packages/labs/test/grpc_web/rollup_test.spec.js new file mode 100644 index 0000000000..ae0bf36661 --- /dev/null +++ b/packages/labs/test/grpc_web/rollup_test.spec.js @@ -0,0 +1,22 @@ +const grpcWeb = require('grpc-web'); +grpcWeb.MethodType = { + UNARY: 'unary' +}; +const bundle = require('build_bazel_rules_nodejs/packages/labs/test/grpc_web/test_es6_bundling/'); + +describe('Rollup', () => { + it('should define Pizza with protobuf API', () => { + expect(bundle.Pizza).toBeDefined(); + + const pizza = new bundle.Pizza(); + pizza.setSize(bundle.PizzaSize.PIZZA_SIZE_LARGE); + + expect(pizza.getSize()).toBe(bundle.PizzaSize.PIZZA_SIZE_LARGE); + expect(Array.isArray(pizza.getToppingIdsList())).toBe(true); + }); + + it('should define DeliveryPerson', () => { + expect(bundle.DeliveryPerson).toBeDefined(); + expect(new bundle.DeliveryPerson()).toBeTruthy(); + }); +}); diff --git a/packages/labs/test/grpc_web/test_bundling.ts b/packages/labs/test/grpc_web/test_bundling.ts new file mode 100644 index 0000000000..f8cb156b0b --- /dev/null +++ b/packages/labs/test/grpc_web/test_bundling.ts @@ -0,0 +1,2 @@ +export {DeliveryPerson} from 'build_bazel_rules_nodejs/packages/labs/test/grpc_web/proto/common/delivery_person_pb'; +export {Pizza, PizzaSize} from 'build_bazel_rules_nodejs/packages/labs/test/grpc_web/proto/common/pizza_pb'; diff --git a/packages/labs/test/protobufjs/BUILD.bazel b/packages/labs/test/protobufjs/BUILD.bazel index 49a20c11b1..45485caea6 100644 --- a/packages/labs/test/protobufjs/BUILD.bazel +++ b/packages/labs/test/protobufjs/BUILD.bazel @@ -1,5 +1,5 @@ load("@npm_bazel_jasmine//:index.from_src.bzl", "jasmine_node_test") -load("@npm_bazel_labs//:index.bzl", "ts_proto_library") +load("@npm_bazel_labs//:index.bzl", "protobufjs_ts_library") load("@npm_bazel_typescript//:index.from_src.bzl", "ts_library") load("@rules_proto//proto:defs.bzl", "proto_library") @@ -17,7 +17,7 @@ proto_library( srcs = [":test.proto"], ) -ts_proto_library( +protobufjs_ts_library( name = "test_ts_proto", deps = [":test_proto"], ) diff --git a/tsconfig.json b/tsconfig.json index 4484aa0268..0d66552006 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { "lib": ["es2017", "dom"], "strict": true, - "target": "es2015" + "baseUrl": ".", + "target": "es2015", } } diff --git a/yarn.lock b/yarn.lock index 8676ca6016..8f4a93ef40 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3482,6 +3482,11 @@ globby@^5.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" +google-protobuf@^3.6.1: + version "3.11.4" + resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.11.4.tgz#598ca405a3cfa917a2132994d008b5932ef42014" + integrity sha512-lL6b04rDirurUBOgsY2+LalI6Evq8eH5TcNzi7TYQ3BsIWelT0KSOQSBsXuavEkNf+odQU6c0lgz3UsZXeNX9Q== + got@^6.7.1: version "6.7.1" resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" @@ -3509,6 +3514,11 @@ graceful-fs@^4.1.15, graceful-fs@^4.2.2, graceful-fs@^4.2.3: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== +grpc-web@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/grpc-web/-/grpc-web-1.0.7.tgz#9e4fbcf63d3734515332ab59e42baa7d0d290015" + integrity sha512-Fkbz1nyvvt6GC6ODcxh9Fen6LLB3OTCgGHzHwM2Eni44SUhzqPz1UQgFp9sfBEfInOhx3yBdwo9ZLjZAmJ+TtA== + handlebars@^4.0.3, handlebars@^4.1.2: version "4.5.3" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.5.3.tgz#5cf75bd8714f7605713511a56be7c349becb0482"