From 0ae5d5aab03366269a511d2d74ffe2b86de3812d Mon Sep 17 00:00:00 2001 From: Adam Michel Date: Sat, 7 Dec 2019 07:54:11 -0800 Subject: [PATCH] ECMAScript Module Exports (#13) * Use ECMAScript module format for .mjs. * Update rollup_bundle tests with Jasmine specs. * Update comment and fix grpc service exports. * Add tests for GRPC service exports. * Add exception for nexted exports and example to tests. --- package.json | 1 + src/change_import_style.ts | 49 +++++++++++++++++-------- test/BUILD.bazel | 14 +++++++ test/proto/common/delivery_person.proto | 6 +++ test/rollup.config.js | 9 ++++- test/rollup_test.spec.js | 47 ++++++++++++++++++++++++ test/test_bundling.ts | 24 ++---------- yarn.lock | 35 ++++++++++++++++++ 8 files changed, 149 insertions(+), 36 deletions(-) create mode 100644 test/rollup_test.spec.js diff --git a/package.json b/package.json index ce6c5e7..f5368b7 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "husky": "^3.0.9", "rollup": "^1.25.1", "rollup-plugin-commonjs": "^10.1.0", + "rollup-plugin-node-resolve": "^5.2.0", "ts-protoc-gen": "^0.11.0", "typescript": "^3.6.4" }, diff --git a/src/change_import_style.ts b/src/change_import_style.ts index b310184..e8f3a42 100644 --- a/src/change_import_style.ts +++ b/src/change_import_style.ts @@ -19,7 +19,7 @@ function main() { const umdContents = convertToUmd(args, initialContents); fs.writeFileSync(args.output_umd_path, umdContents, 'utf8'); - const commonJsContents = processCommonJs(args, initialContents); + const commonJsContents = convertToESM(args, initialContents); fs.writeFileSync(args.output_es6_path, commonJsContents, 'utf8'); } @@ -62,27 +62,46 @@ function convertToUmd(args: any, initialContents: string): string { }, initialContents); } -function processCommonJs(args: any, initialContents: string): string { - // Rollup can't resolve the commonjs exports when using goog.object.extend so we replace it with: - // 'exports.MyProto = proto.namespace.MyProto;' +// 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: any, initialContents: string): string { const replaceGoogExtendWithExports = (contents: string) => { - const googSymbolRegex = /goog.exportSymbol\('(.*\.([A-z0-9_]+))',.*;/g; - let match; - const symbols = []; - while (match = googSymbolRegex.exec(initialContents)) { - symbols.push([match[2], match[1]]); - } - - const exportSymbols = symbols.reduce((currentSymbols, symbol) => { - return currentSymbols + `exports.${symbol[0]} = ${symbol[1]};\n`; - }, ''); - return contents.replace(/goog.object.extend\(exports, .*;/g, `${exportSymbols}`); + return contents.replace(/goog\.object\.extend\(exports, ([\w\.]+)\);/g, (_, packageName: string) => { + const exportSymbols = /goog\.exportSymbol\('([\w\.]+)',.*\);/g; + const symbols = []; + + let match: RegExpExecArray; + 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 replaceRequiresWithImports = (contents: string) => { + return contents.replace(/var ([\w\d_]+) = require\((['"][\w\d@/_-]+['"])\);/g, 'import * as $1 from $2;'); }; + const replaceRequiresWithSubpackageImports = (contents: string) => { + return contents.replace(/var ([\w\d_]+) = require\((['"][\w\d@/_-]+['"])\)\.([\w\d_]+);/g, 'import * as $1 from $2;') + } + + const replaceCJSExportsWithECMAExports = (contents: string) => { + return contents.replace(/exports\.([\w\d_]+) = .*;/g, 'export { $1 };') + } + const transformations: ((c: string) => string)[] = [ replaceRecursiveFilePaths(args), removeJsExtensionsFromRequires, replaceGoogExtendWithExports, + replaceRequiresWithImports, + replaceRequiresWithSubpackageImports, + replaceCJSExportsWithECMAExports, ]; return transformations.reduce((currentContents, transform) => { return transform(currentContents); diff --git a/test/BUILD.bazel b/test/BUILD.bazel index 3ce58d8..21633e4 100644 --- a/test/BUILD.bazel +++ b/test/BUILD.bazel @@ -110,6 +110,7 @@ rollup_bundle( deps = [ ":test_bundling_lib", ], + format = "cjs", ) ts_library( @@ -118,7 +119,20 @@ ts_library( tsconfig = ":test_bundling_tsconfig.json", deps = [ "//test/proto:naming_styles_ts_proto", + "//test/proto/common:pizza_ts_proto", + "//test/proto:pizza_service_ts_proto", "//test/proto/common:delivery_person_ts_proto", "@npm//google-protobuf", + "@npm//@improbable-eng/grpc-web", ], ) + +jasmine_node_test( + name = "rollup_test", + srcs = [ + ":rollup_test.spec.js" + ], + data = [ + ":test_es6_bundling" + ] +) diff --git a/test/proto/common/delivery_person.proto b/test/proto/common/delivery_person.proto index 48ce806..851bc4b 100644 --- a/test/proto/common/delivery_person.proto +++ b/test/proto/common/delivery_person.proto @@ -10,4 +10,10 @@ message DeliveryPerson { // 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/test/rollup.config.js b/test/rollup.config.js index 1071bcd..89a556a 100644 --- a/test/rollup.config.js +++ b/test/rollup.config.js @@ -1,9 +1,16 @@ const commonjs = require('rollup-plugin-commonjs'); +const nodeRequire = require('rollup-plugin-node-resolve'); module.exports = { plugins: [ + nodeRequire(), commonjs({ - extensions: ['.js', '.mjs'], + // Temporary fix until https://github.com/improbable-eng/grpc-web/issues/369 is resolved. + namedExports: { + './node_modules/@improbable-eng/grpc-web/dist/grpc-web-client.js': [ + 'grpc', + ], + } }), ], }; diff --git a/test/rollup_test.spec.js b/test/rollup_test.spec.js new file mode 100644 index 0000000..27fc402 --- /dev/null +++ b/test/rollup_test.spec.js @@ -0,0 +1,47 @@ +const bundle = require('rules_typescript_proto/test/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(); + }); + + it('should define PizzaService', () => { + expect(bundle.PizzaService).toBeDefined(); + expect(bundle.PizzaService.serviceName).toBe('test.bazel.proto.PizzaService'); + expect(bundle.PizzaService.OrderPizza.methodName).toBe('OrderPizza'); + }); + + it('should define PizzaServiceClient', () => { + expect(bundle.PizzaServiceClient).toBeDefined(); + const client = new bundle.PizzaServiceClient('http://localhost', {}); + expect(typeof client.orderPizza).toBe('function'); + }); + + it('should follow expected naming styles', () => { + expect(new bundle.alllowercase().setTest(1)).toBeTruthy(); + expect(new bundle.ALLUPPERCASE().setTest(1)).toBeTruthy(); + expect(new bundle.lowerCamelCase().setTest(1)).toBeTruthy(); + expect(new bundle.UpperCamelCase().setTest(1)).toBeTruthy(); + expect(new bundle.snake_case_snake_case().setTest(1)).toBeTruthy(); + expect(new bundle.Upper_snake_Case().setTest(1)).toBeTruthy(); + expect(new bundle.M2M().setTest(1)).toBeTruthy(); + expect(new bundle.M_2M().setTest(1)).toBeTruthy(); + expect(new bundle.M2_M().setTest(1)).toBeTruthy(); + expect(new bundle.M2M_().setTest(1)).toBeTruthy(); + expect(new bundle.m_22M().setTest(1)).toBeTruthy(); + expect(new bundle.m42_M().setTest(1)).toBeTruthy(); + expect(new bundle.m24M_().setTest(1)).toBeTruthy(); + expect(new bundle.M9().setTest(1)).toBeTruthy(); + }); +}); diff --git a/test/test_bundling.ts b/test/test_bundling.ts index 663ab66..f82dada 100644 --- a/test/test_bundling.ts +++ b/test/test_bundling.ts @@ -1,20 +1,4 @@ -import {DeliveryPerson} from 'rules_typescript_proto/test/proto/common/delivery_person_pb'; -import {alllowercase, ALLUPPERCASE, lowerCamelCase, m24M_, M2_M, M2M, M2M_, m42_M, M9, m_22M, M_2M, snake_case_snake_case, Upper_snake_Case, UpperCamelCase,} from 'rules_typescript_proto/test/proto/naming_styles_pb'; - -const person = new DeliveryPerson(); -console.log(person); - -console.log(new alllowercase().setTest(1)); -console.log(new ALLUPPERCASE().setTest(1)); -console.log(new lowerCamelCase().setTest(1)); -console.log(new UpperCamelCase().setTest(1)); -console.log(new snake_case_snake_case().setTest(1)); -console.log(new Upper_snake_Case().setTest(1)); -console.log(new M2M().setTest(1)); -console.log(new M_2M().setTest(1)); -console.log(new M2_M().setTest(1)); -console.log(new M2M_().setTest(1)); -console.log(new m_22M().setTest(1)); -console.log(new m42_M().setTest(1)); -console.log(new m24M_().setTest(1)); -console.log(new M9().setTest(1)); +export {alllowercase, ALLUPPERCASE, lowerCamelCase, m24M_, M2_M, M2M, M2M_, m42_M, M9, m_22M, M_2M, snake_case_snake_case, Upper_snake_Case, UpperCamelCase,} from 'rules_typescript_proto/test/proto/naming_styles_pb'; +export { DeliveryPerson } from 'rules_typescript_proto/test/proto/common/delivery_person_pb'; +export { Pizza, PizzaSize } from 'rules_typescript_proto/test/proto/common/pizza_pb'; +export { PizzaService, PizzaServiceClient } from 'rules_typescript_proto/test/proto/pizza_service_pb_service'; diff --git a/yarn.lock b/yarn.lock index b4e2224..90ef92e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -159,6 +159,13 @@ resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== +"@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" "*" + abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -419,6 +426,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== + bytes@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" @@ -1467,6 +1479,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" @@ -2539,6 +2556,13 @@ resolve@^1.1.6, resolve@^1.10.0, resolve@^1.11.0: dependencies: path-parse "^1.0.6" +resolve@^1.11.1: + version "1.13.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.13.0.tgz#e879eb397efb40511056ede7300b6ac28c51290b" + integrity sha512-HHZ3hmOrk5SvybTb18xq4Ek2uLqLO5/goFCYUyvn26nWox4hdlKlfC/+dChIZ6qc4ZeYcN9ekTz0yyHsFgumMw== + 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" @@ -2567,6 +2591,17 @@ rollup-plugin-commonjs@^10.1.0: 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"