THRIFT-5811: Add ESM support to nodejs codegen
Client: nodejs
Patch: Cameron Martin <cameronm@graphcore.ai>
This closes #3083
This adds a flag to the JS generator to output ES modules instead of CommonJS. This is only valid when targeting node. A lot of the changes here are to test this.
The `testAll.sh` script now generates an ES module version of the services and types, and tests the client and the server with these. This has a few knock-on effects. Firstly, any module that imports a generated ES module must itself be an ES module, since CommonJS modules cannot import ES modules. ES modules also do not support `NODE_PATH`, so instead the tests directory is converted into a node package with a `file:` dependency on the root thrift package.
diff --git a/compiler/cpp/src/thrift/generate/t_js_generator.cc b/compiler/cpp/src/thrift/generate/t_js_generator.cc
index 27240e4..402ff50 100644
--- a/compiler/cpp/src/thrift/generate/t_js_generator.cc
+++ b/compiler/cpp/src/thrift/generate/t_js_generator.cc
@@ -68,6 +68,7 @@
gen_jquery_ = false;
gen_ts_ = false;
gen_es6_ = false;
+ gen_esm_ = false;
gen_episode_file_ = false;
bool with_ns_ = false;
@@ -83,6 +84,8 @@
with_ns_ = true;
} else if( iter->first.compare("es6") == 0) {
gen_es6_ = true;
+ } else if ( iter->first.compare("esm") == 0) {
+ gen_esm_ = true;
} else if( iter->first.compare("imports") == 0) {
parse_imports(program, iter->second);
} else if (iter->first.compare("thrift_package_output_directory") == 0) {
@@ -105,6 +108,10 @@
throw std::invalid_argument("invalid switch: [-gen js:with_ns] is only valid when using node.js");
}
+ if (!gen_node_ && gen_esm_) {
+ throw std::invalid_argument("invalid switch: [-gen js:esm] is only valid when using node.js");
+ }
+
// Depending on the processing flags, we will update these to be ES6 compatible
js_const_type_ = "var ";
js_let_type_ = "var ";
@@ -278,13 +285,6 @@
return js_namespace(p);
}
- std::string js_export_namespace(t_program* p) {
- if (gen_node_) {
- return "exports.";
- }
- return js_namespace(p);
- }
-
bool has_js_namespace(t_program* p) {
if (no_ns_) {
return false;
@@ -375,6 +375,11 @@
bool gen_es6_;
/**
+ * True if we should generate ES modules, instead of CommonJS.
+ */
+ bool gen_esm_;
+
+ /**
* True if we will generate an episode file.
*/
bool gen_episode_file_;
@@ -452,7 +457,7 @@
f_episode_.open(f_episode_file_path);
}
- const auto f_types_name = outdir + program_->get_name() + "_types.js";
+ const auto f_types_name = outdir + program_->get_name() + "_types" + (gen_esm_ ? ".mjs" : ".js");
f_types_.open(f_types_name.c_str());
if (gen_episode_file_) {
const auto types_module = program_->get_name() + "_types";
@@ -478,7 +483,13 @@
}
if (gen_node_) {
- f_types_ << js_const_type_ << "ttypes = module.exports = {};" << '\n';
+ if (gen_esm_) {
+ // Import the current module, so we can reference it as ttypes. This is
+ // fine in ESM, because it allows circular imports.
+ f_types_ << "import * as ttypes from './" + program_->get_name() + "_types.mjs';" << '\n';
+ } else {
+ f_types_ << js_const_type_ << "ttypes = module.exports = {};" << '\n';
+ }
}
string pns;
@@ -507,12 +518,26 @@
*/
string t_js_generator::js_includes() {
if (gen_node_) {
- string result = js_const_type_ + "thrift = require('thrift');\n"
- + js_const_type_ + "Thrift = thrift.Thrift;\n";
- if (!gen_es6_) {
- result += js_const_type_ + "Q = thrift.Q;\n";
+ string result;
+
+ if (gen_esm_) {
+ result += "import { Thrift } from 'thrift';\n";
+ } else {
+ result += js_const_type_ + "thrift = require('thrift');\n"
+ + js_const_type_ + "Thrift = thrift.Thrift;\n";
}
- result += js_const_type_ + "Int64 = require('node-int64');\n";
+ if (!gen_es6_) {
+ if (gen_esm_) {
+ result += "import { Q } from 'thrift';\n";
+ } else {
+ result += js_const_type_ + "Q = thrift.Q;\n";
+ }
+ }
+ if (gen_esm_) {
+ result += "import Int64 from 'node-int64';";
+ } else {
+ result += js_const_type_ + "Int64 = require('node-int64');\n";
+ }
return result;
}
string result = "if (typeof Int64 === 'undefined' && typeof require === 'function') {\n " + js_const_type_ + "Int64 = require('node-int64');\n}\n";
@@ -556,7 +581,11 @@
if (gen_node_) {
const vector<t_program*>& includes = program_->get_includes();
for (auto include : includes) {
- result += js_const_type_ + make_valid_nodeJs_identifier(include->get_name()) + "_ttypes = require('" + get_import_path(include) + "');\n";
+ if (gen_esm_) {
+ result += "import * as " + make_valid_nodeJs_identifier(include->get_name()) + "_ttypes from '" + get_import_path(include) + "';\n";
+ } else {
+ result += js_const_type_ + make_valid_nodeJs_identifier(include->get_name()) + "_ttypes = require('" + get_import_path(include) + "');\n";
+ }
}
if (includes.size() > 0) {
result += "\n";
@@ -590,17 +619,17 @@
string t_js_generator::get_import_path(t_program* program) {
const string import_file_name(program->get_name() + "_types");
+ const string import_file_name_with_extension = import_file_name + (gen_esm_ ? ".mjs" : ".js");
+
if (program->get_recursive()) {
- return "./" + import_file_name;
+ return "./" + import_file_name_with_extension;
}
- const string import_file_name_with_extension = import_file_name + ".js";
-
- auto module_name_and_import_path_iterator = module_name_2_import_path.find(import_file_name);
- if (module_name_and_import_path_iterator != module_name_2_import_path.end()) {
- return module_name_and_import_path_iterator->second;
- }
- return "./" + import_file_name;
+ auto module_name_and_import_path_iterator = module_name_2_import_path.find(import_file_name);
+ if (module_name_and_import_path_iterator != module_name_2_import_path.end()) {
+ return module_name_and_import_path_iterator->second;
+ }
+ return "./" + import_file_name_with_extension;
}
/**
@@ -638,7 +667,11 @@
* @param tenum The enumeration
*/
void t_js_generator::generate_enum(t_enum* tenum) {
- f_types_ << js_type_namespace(tenum->get_program()) << tenum->get_name() << " = {" << '\n';
+ if (gen_esm_) {
+ f_types_ << "export const " << tenum->get_name() << " = {" << '\n';
+ } else {
+ f_types_ << js_type_namespace(tenum->get_program()) << tenum->get_name() << " = {" << '\n';
+ }
if (gen_ts_) {
f_types_ts_ << ts_print_doc(tenum) << ts_indent() << ts_declare() << "enum "
@@ -680,7 +713,11 @@
string name = tconst->get_name();
t_const_value* value = tconst->get_value();
- f_types_ << js_type_namespace(program_) << name << " = ";
+ if (gen_esm_) {
+ f_types_ << "export const " << name << " = ";
+ } else {
+ f_types_ << js_type_namespace(program_) << name << " = ";
+ }
f_types_ << render_const_value(type, value) << ";" << '\n';
if (gen_ts_) {
@@ -859,9 +896,18 @@
vector<t_field*>::const_iterator m_iter;
if (gen_node_) {
+ string commonjs_export = "";
+
+ if (is_exported) {
+ if (gen_esm_) {
+ out << "export ";
+ } else {
+ commonjs_export = " = module.exports." + tstruct->get_name();
+ }
+ }
+
string prefix = has_js_namespace(tstruct->get_program()) ? js_namespace(tstruct->get_program()) : js_const_type_;
- out << prefix << tstruct->get_name() <<
- (is_exported ? " = module.exports." + tstruct->get_name() : "");
+ out << prefix << tstruct->get_name() << commonjs_export;
if (gen_ts_) {
f_types_ts_ << ts_print_doc(tstruct) << ts_indent() << ts_declare() << "class "
<< tstruct->get_name() << (is_exception ? " extends Thrift.TException" : "")
@@ -1198,7 +1244,7 @@
* @param tservice The service definition
*/
void t_js_generator::generate_service(t_service* tservice) {
- string f_service_name = get_out_dir() + service_name_ + ".js";
+ string f_service_name = get_out_dir() + service_name_ + (gen_esm_ ? ".mjs" : ".js");
f_service_.open(f_service_name.c_str());
if (gen_episode_file_) {
f_episode_ << service_name_ << ":" << thrift_package_output_directory_ << "/" << service_name_ << '\n';
@@ -1282,7 +1328,11 @@
<< tservice->get_extends()->get_name() << "');" << '\n';
}
- f_service_ << js_const_type_ << "ttypes = require('./" + program_->get_name() + "_types');" << '\n';
+ if (gen_esm_) {
+ f_service_ << "import * as ttypes from './" + program_->get_name() + "_types.mjs';" << '\n';
+ } else {
+ f_service_ << js_const_type_ << "ttypes = require('./" + program_->get_name() + "_types');" << '\n';
+ }
}
generate_service_helpers(tservice);
@@ -1317,27 +1367,28 @@
vector<t_function*> functions = tservice->get_functions();
vector<t_function*>::iterator f_iter;
- if (gen_node_) {
- string prefix = has_js_namespace(tservice->get_program()) ? js_namespace(tservice->get_program()) : js_const_type_;
- f_service_ << prefix << service_name_ << "Processor = " << "exports.Processor";
- if (gen_ts_) {
- f_service_ts_ << '\n' << "declare class Processor ";
- if (tservice->get_extends() != nullptr) {
- f_service_ts_ << "extends " << tservice->get_extends()->get_name() << ".Processor ";
- }
- f_service_ts_ << "{" << '\n';
- indent_up();
-
- if(tservice->get_extends() == nullptr) {
- f_service_ts_ << ts_indent() << "private _handler: object;" << '\n' << '\n';
- }
- f_service_ts_ << ts_indent() << "constructor(handler: object);" << '\n';
- f_service_ts_ << ts_indent() << "process(input: thrift.TProtocol, output: thrift.TProtocol): void;" << '\n';
- indent_down();
- }
+ std::string service_var;
+ if (!gen_node_ || has_js_namespace(tservice->get_program())) {
+ service_var = js_namespace(tservice->get_program()) + service_name_ + "Processor";
+ f_service_ << service_var;
} else {
- f_service_ << js_namespace(tservice->get_program()) << service_name_ << "Processor = "
- << "exports.Processor";
+ service_var = service_name_ + "Processor";
+ f_service_ << js_const_type_ << service_var;
+ };
+ if (gen_node_ && gen_ts_) {
+ f_service_ts_ << '\n' << "declare class Processor ";
+ if (tservice->get_extends() != nullptr) {
+ f_service_ts_ << "extends " << tservice->get_extends()->get_name() << ".Processor ";
+ }
+ f_service_ts_ << "{" << '\n';
+ indent_up();
+
+ if(tservice->get_extends() == nullptr) {
+ f_service_ts_ << ts_indent() << "private _handler: object;" << '\n' << '\n';
+ }
+ f_service_ts_ << ts_indent() << "constructor(handler: object);" << '\n';
+ f_service_ts_ << ts_indent() << "process(input: thrift.TProtocol, output: thrift.TProtocol): void;" << '\n';
+ indent_down();
}
bool is_subclass_service = tservice->get_extends() != nullptr;
@@ -1419,6 +1470,12 @@
if (gen_node_ && gen_ts_) {
f_service_ts_ << "}" << '\n';
}
+
+ if(gen_esm_) {
+ f_service_ << "export { " << service_var << " as Processor };" << '\n';
+ } else {
+ f_service_ << "exports.Processor = " << service_var << ";" << '\n';
+ }
}
/**
@@ -1702,9 +1759,10 @@
bool is_subclass_service = tservice->get_extends() != nullptr;
+ string client_var = js_namespace(tservice->get_program()) + service_name_ + "Client";
if (gen_node_) {
- string prefix = has_js_namespace(tservice->get_program()) ? js_namespace(tservice->get_program()) : js_const_type_;
- f_service_ << prefix << service_name_ << "Client = " << "exports.Client";
+ string prefix = has_js_namespace(tservice->get_program()) ? "" : js_const_type_;
+ f_service_ << prefix << client_var;
if (gen_ts_) {
f_service_ts_ << ts_print_doc(tservice) << ts_indent() << ts_declare() << "class "
<< "Client ";
@@ -1714,8 +1772,7 @@
f_service_ts_ << "{" << '\n';
}
} else {
- f_service_ << js_namespace(tservice->get_program()) << service_name_
- << "Client";
+ f_service_ << client_var;
if (gen_ts_) {
f_service_ts_ << ts_print_doc(tservice) << ts_indent() << ts_declare() << "class "
<< service_name_ << "Client ";
@@ -2204,6 +2261,12 @@
indent_down();
f_service_ << "};" << '\n';
}
+
+ if(gen_esm_) {
+ f_service_ << "export { " << client_var << " as Client };" << '\n';
+ } else if(gen_node_) {
+ f_service_ << "exports.Client = " << client_var << ";" << '\n';
+ }
}
std::string t_js_generator::render_recv_throw(std::string var) {
diff --git a/eslint.config.mjs b/eslint.config.mjs
index a27b211..2f70a88 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -24,7 +24,7 @@
...globals.node,
},
- ecmaVersion: 2017,
+ ecmaVersion: 2022,
sourceType: "commonjs",
},
@@ -41,4 +41,10 @@
],
},
},
+ {
+ files: ["**/*.mjs"],
+ languageOptions: {
+ sourceType: "module",
+ },
+ },
];
diff --git a/lib/nodejs/Makefile.am b/lib/nodejs/Makefile.am
index 9503f04..5be8161 100644
--- a/lib/nodejs/Makefile.am
+++ b/lib/nodejs/Makefile.am
@@ -20,9 +20,14 @@
stubs: $(top_srcdir)/test/v0.16/ThriftTest.thrift
$(THRIFT) --gen js:node -o test/ $(top_srcdir)/test/v0.16/ThriftTest.thrift
-deps: $(top_srcdir)/package.json
+deps-root: $(top_srcdir)/package.json
$(NPM) install $(top_srcdir)/ || $(NPM) install $(top_srcdir)/
+deps-test: test/package.json test/package-lock.json
+ cd test/ && $(NPM) install && cd ..
+
+deps: deps-root deps-test
+
all-local: deps
precross: deps stubs
diff --git a/lib/nodejs/test/binary.test.js b/lib/nodejs/test/binary.test.js
index 343d39a..1e749d8 100644
--- a/lib/nodejs/test/binary.test.js
+++ b/lib/nodejs/test/binary.test.js
@@ -18,7 +18,7 @@
*/
const test = require("tape");
-const binary = require("thrift/binary");
+const binary = require("thrift/lib/nodejs/lib/thrift/binary");
const cases = {
"Should read signed byte": function (assert) {
diff --git a/lib/nodejs/test/client.js b/lib/nodejs/test/client.mjs
similarity index 90%
rename from lib/nodejs/test/client.js
rename to lib/nodejs/test/client.mjs
index 617039b..6200dc6 100644
--- a/lib/nodejs/test/client.js
+++ b/lib/nodejs/test/client.mjs
@@ -19,17 +19,19 @@
* under the License.
*/
-const assert = require("assert");
-const thrift = require("thrift");
-const helpers = require("./helpers");
+import assert from "assert";
+import thrift from "thrift";
+import helpers from "./helpers.js";
-const ThriftTest = require(`./${helpers.genPath}/ThriftTest`);
-const ThriftTestDriver = require("./test_driver").ThriftTestDriver;
-const ThriftTestDriverPromise =
- require("./test_driver").ThriftTestDriverPromise;
-const SecondService = require(`./${helpers.genPath}/SecondService`);
+const ThriftTest = await import(
+ `./${helpers.genPath}/ThriftTest.${helpers.moduleExt}`
+);
+import { ThriftTestDriver, ThriftTestDriverPromise } from "./test_driver.mjs";
+const SecondService = await import(
+ `./${helpers.genPath}/SecondService.${helpers.moduleExt}`
+);
-const { program } = require("commander");
+import { program } from "commander";
program
.option(
@@ -55,6 +57,7 @@
)
.option("--es6", "Use es6 code")
.option("--es5", "Use es5 code")
+ .option("--esm", "Use es modules")
.parse(process.argv);
const opts = program.opts();
@@ -171,4 +174,4 @@
});
}
-exports.expressoTest = function () {};
+export const expressoTest = function () {};
diff --git a/lib/nodejs/test/helpers.js b/lib/nodejs/test/helpers.js
index 51a0523..1ece2f1 100644
--- a/lib/nodejs/test/helpers.js
+++ b/lib/nodejs/test/helpers.js
@@ -18,7 +18,7 @@
*/
"use strict";
-const thrift = require("../lib/thrift");
+const thrift = require("thrift");
module.exports.transports = {
buffered: thrift.TBufferedTransport,
@@ -32,7 +32,36 @@
header: thrift.THeaderProtocol,
};
-module.exports.ecmaMode = process.argv.includes("--es6") ? "es6" : "es5";
-module.exports.genPath = process.argv.includes("--es6")
- ? "gen-nodejs-es6"
- : "gen-nodejs";
+const variant = (function () {
+ if (process.argv.includes("--es6")) {
+ return "es6";
+ } else if (process.argv.includes("--esm")) {
+ return "esm";
+ } else {
+ return "es5";
+ }
+})();
+
+module.exports.ecmaMode = ["esm", "es6"].includes(variant) ? "es6" : "es5";
+const genPath = (module.exports.genPath = (function () {
+ if (variant == "es5") {
+ return "gen-nodejs";
+ } else {
+ return `gen-nodejs-${variant}`;
+ }
+})());
+
+const moduleExt = (module.exports.moduleExt = variant === "esm" ? "mjs" : "js");
+
+/**
+ * Imports a types module, correctly handling the differences in esm and commonjs
+ */
+module.exports.importTypes = async function (filename) {
+ const typesModule = await import(`./${genPath}/${filename}.${moduleExt}`);
+
+ if (variant === "esm") {
+ return typesModule;
+ } else {
+ return typesModule.default;
+ }
+};
diff --git a/lib/nodejs/test/include.test.mjs b/lib/nodejs/test/include.test.mjs
new file mode 100644
index 0000000..70c7b24
--- /dev/null
+++ b/lib/nodejs/test/include.test.mjs
@@ -0,0 +1,18 @@
+import test from "tape";
+import { IncludeTest as IncludeTestEs5 } from "./gen-nodejs/Include_types.js";
+import { IncludeTest as IncludeTestEs6 } from "./gen-nodejs-es6/Include_types.js";
+import { IncludeTest as IncludeTestEsm } from "./gen-nodejs-esm/Include_types.mjs";
+
+function constructTest(classVariant) {
+ return function (t) {
+ const obj = new classVariant({ bools: { im_true: true, im_false: false } });
+
+ t.assert(obj.bools.im_true === true);
+ t.assert(obj.bools.im_false === false);
+ t.end();
+ };
+}
+
+test("construct es5", constructTest(IncludeTestEs5));
+test("construct es6", constructTest(IncludeTestEs6));
+test("construct esm", constructTest(IncludeTestEsm));
diff --git a/lib/nodejs/test/package-lock.json b/lib/nodejs/test/package-lock.json
new file mode 100644
index 0000000..e7f9543
--- /dev/null
+++ b/lib/nodejs/test/package-lock.json
@@ -0,0 +1,52 @@
+{
+ "name": "test",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "devDependencies": {
+ "thrift": "file:../../.."
+ }
+ },
+ "../../..": {
+ "version": "0.22.0",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "browser-or-node": "^1.2.1",
+ "isomorphic-ws": "^4.0.1",
+ "node-int64": "^0.4.0",
+ "q": "^1.5.0",
+ "ws": "^5.2.3"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.18.0",
+ "@types/node": "^22.10.5",
+ "@types/node-int64": "^0.4.29",
+ "@types/q": "^1.5.1",
+ "buffer-equals": "^1.0.4",
+ "commander": "^13.0.0",
+ "connect": "^3.6.6",
+ "eslint": "^9.18.0",
+ "eslint-config-prettier": "^10.0.1",
+ "eslint-plugin-prettier": "^5.2.1",
+ "globals": "^15.14.0",
+ "html-validator-cli": "^2.0.0",
+ "jsdoc": "^4.0.2",
+ "json-int64": "^1.0.2",
+ "nyc": "^15.0.0",
+ "prettier": "^3.4.2",
+ "tape": "^4.9.0",
+ "typescript": "^5.7.2",
+ "utf-8-validate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">= 10.18.0"
+ }
+ },
+ "node_modules/thrift": {
+ "resolved": "../../..",
+ "link": true
+ }
+ }
+}
diff --git a/lib/nodejs/test/package.json b/lib/nodejs/test/package.json
new file mode 100644
index 0000000..22220ec
--- /dev/null
+++ b/lib/nodejs/test/package.json
@@ -0,0 +1,5 @@
+{
+ "devDependencies": {
+ "thrift": "file:../../.."
+ }
+}
diff --git a/lib/nodejs/test/server.js b/lib/nodejs/test/server.mjs
similarity index 84%
rename from lib/nodejs/test/server.js
rename to lib/nodejs/test/server.mjs
index b56bea7..7a3c593 100644
--- a/lib/nodejs/test/server.js
+++ b/lib/nodejs/test/server.mjs
@@ -19,11 +19,11 @@
* under the License.
*/
-const fs = require("fs");
-const path = require("path");
-const thrift = require("../lib/thrift");
-const { program } = require("commander");
-const helpers = require("./helpers");
+import fs from "fs";
+import path from "path";
+import thrift from "thrift";
+import { program } from "commander";
+import helpers from "./helpers.js";
program
.option(
@@ -47,11 +47,16 @@
.option("--callback", "test with callback style functions")
.option("--es6", "Use es6 code")
.option("--es5", "Use es5 code")
+ .option("--esm", "Use es modules")
.parse(process.argv);
-const ThriftTest = require(`./${helpers.genPath}/ThriftTest`);
-const SecondService = require(`./${helpers.genPath}/SecondService`);
-const { ThriftTestHandler } = require("./test_handler");
+const ThriftTest = await import(
+ `./${helpers.genPath}/ThriftTest.${helpers.moduleExt}`
+);
+const SecondService = await import(
+ `./${helpers.genPath}/SecondService.${helpers.moduleExt}`
+);
+import { ThriftTestHandler } from "./test_handler.mjs";
const opts = program.opts();
const port = opts.port;
@@ -114,8 +119,8 @@
type === "websocket"
) {
options.tls = {
- key: fs.readFileSync(path.resolve(__dirname, "server.key")),
- cert: fs.readFileSync(path.resolve(__dirname, "server.crt")),
+ key: fs.readFileSync(path.resolve(import.meta.dirname, "server.key")),
+ cert: fs.readFileSync(path.resolve(import.meta.dirname, "server.crt")),
};
}
}
diff --git a/lib/nodejs/test/test-cases.js b/lib/nodejs/test/test-cases.mjs
similarity index 85%
rename from lib/nodejs/test/test-cases.js
rename to lib/nodejs/test/test-cases.mjs
index 98077f7..543e353 100644
--- a/lib/nodejs/test/test-cases.js
+++ b/lib/nodejs/test/test-cases.mjs
@@ -19,13 +19,14 @@
"use strict";
-const helpers = require("./helpers");
-const ttypes = require(`./${helpers.genPath}/ThriftTest_types`);
-const Int64 = require("node-int64");
+import helpers from "./helpers.js";
+import Int64 from "node-int64";
+
+const ttypes = await helpers.importTypes(`ThriftTest_types`);
//all Languages in UTF-8
/*jshint -W100 */
-const stringTest = (module.exports.stringTest =
+export const stringTest =
"Afrikaans, Alemannisch, Aragonés, العربية, مصرى, " +
"Asturianu, Aymar aru, Azərbaycan, Башҡорт, Boarisch, Žemaitėška, " +
"Беларуская, Беларуская (тарашкевіца), Български, Bamanankan, " +
@@ -50,27 +51,27 @@
"Svenska, Kiswahili, தமிழ், తెలుగు, Тоҷикӣ, ไทย, Türkmençe, Tagalog, " +
"Türkçe, Татарча/Tatarça, Українська, اردو, Tiếng Việt, Volapük, " +
"Walon, Winaray, 吴语, isiXhosa, ייִדיש, Yorùbá, Zeêuws, 中文, " +
- "Bân-lâm-gú, 粵語");
+ "Bân-lâm-gú, 粵語";
/*jshint +W100 */
-const specialCharacters = (module.exports.specialCharacters =
+export const specialCharacters =
'quote: " backslash:' +
" forwardslash-escaped: / " +
" backspace: \b formfeed: \f newline: \n return: \r tab: " +
' now-all-of-them-together: "\\/\b\n\r\t' +
" now-a-bunch-of-junk: !@#$%&()(&%$#{}{}<><><" +
- ' char-to-test-json-parsing: ]] "]] \\" }}}{ [[[ ');
+ ' char-to-test-json-parsing: ]] "]] \\" }}}{ [[[ ';
-const mapTestInput = (module.exports.mapTestInput = {
+export const mapTestInput = {
a: "123",
"a b": "with spaces ",
same: "same",
0: "numeric key",
longValue: stringTest,
stringTest: "long key",
-});
+};
-const simple = [
+export const simple = [
["testVoid", undefined],
["testString", "Test"],
["testString", ""],
@@ -103,32 +104,32 @@
mapout[i] = i - 10;
}
-const deep = [
+export const deep = [
[
"testList",
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
],
];
-const deepUnordered = [
+export const deepUnordered = [
["testMap", mapout],
["testSet", [1, 2, 3]],
["testStringMap", mapTestInput],
];
-const out = new ttypes.Xtruct({
+export const out = new ttypes.Xtruct({
string_thing: "Zero",
byte_thing: 1,
i32_thing: -3,
i64_thing: 1000000,
});
-const out2 = new ttypes.Xtruct2();
+export const out2 = new ttypes.Xtruct2();
out2.byte_thing = 1;
out2.struct_thing = out;
out2.i32_thing = 5;
-const crazy = new ttypes.Insanity({
+export const crazy = new ttypes.Insanity({
userMap: { 5: 5, 8: 8 },
xtructs: [
new ttypes.Xtruct({
@@ -146,7 +147,7 @@
],
});
-const crazy2 = new ttypes.Insanity({
+export const crazy2 = new ttypes.Insanity({
userMap: { 5: 5, 8: 8 },
xtructs: [
{
@@ -164,17 +165,7 @@
],
});
-const insanity = {
+export const insanity = {
1: { 2: crazy, 3: crazy },
2: { 6: { userMap: {}, xtructs: [] } },
};
-
-module.exports.simple = simple;
-module.exports.deep = deep;
-module.exports.deepUnordered = deepUnordered;
-
-module.exports.out = out;
-module.exports.out2 = out2;
-module.exports.crazy = crazy;
-module.exports.crazy2 = crazy2;
-module.exports.insanity = insanity;
diff --git a/lib/nodejs/test/testAll.sh b/lib/nodejs/test/testAll.sh
index 144832e..a3baa6e 100755
--- a/lib/nodejs/test/testAll.sh
+++ b/lib/nodejs/test/testAll.sh
@@ -35,25 +35,23 @@
COUNT=0
-export NODE_PATH="${DIR}:${DIR}/../lib:${NODE_PATH}"
-
testServer()
{
- echo " [ECMA $1] Testing $2 Client/Server with protocol $3 and transport $4 $5";
+ echo " [Variant: $1] Testing $2 Client/Server with protocol $3 and transport $4 $5";
RET=0
if [ -n "${COVER}" ]; then
- ${ISTANBUL} cover ${DIR}/server.js --dir ${REPORT_PREFIX}${COUNT} --handle-sigint -- --type $2 -p $3 -t $4 $5 &
+ ${ISTANBUL} cover ${DIR}/server.mjs --dir ${REPORT_PREFIX}${COUNT} --handle-sigint -- --type $2 -p $3 -t $4 $5 &
COUNT=$((COUNT+1))
else
- node ${DIR}/server.js --${1} --type $2 -p $3 -t $4 $5 &
+ node ${DIR}/server.mjs --${1} --type $2 -p $3 -t $4 $5 &
fi
SERVERPID=$!
sleep 0.1
if [ -n "${COVER}" ]; then
- ${ISTANBUL} cover ${DIR}/client.js --dir ${REPORT_PREFIX}${COUNT} -- --${1} --type $2 -p $3 -t $4 $5 || RET=1
+ ${ISTANBUL} cover ${DIR}/client.mjs --dir ${REPORT_PREFIX}${COUNT} -- --${1} --type $2 -p $3 -t $4 $5 || RET=1
COUNT=$((COUNT+1))
else
- node ${DIR}/client.js --${1} --type $2 -p $3 -t $4 $5 || RET=1
+ node ${DIR}/client.mjs --${1} --type $2 -p $3 -t $4 $5 || RET=1
fi
kill -2 $SERVERPID || RET=1
wait $SERVERPID
@@ -90,10 +88,17 @@
${THRIFT_COMPILER} -o ${DIR} --gen js:node ${THRIFT_FILES_DIR}/v0.16/ThriftTest.thrift
${THRIFT_COMPILER} -o ${DIR} --gen js:node ${THRIFT_FILES_DIR}/JsDeepConstructorTest.thrift
${THRIFT_COMPILER} -o ${DIR} --gen js:node ${THRIFT_FILES_DIR}/Int64Test.thrift
+${THRIFT_COMPILER} -o ${DIR} --gen js:node ${THRIFT_FILES_DIR}/Include.thrift
mkdir ${DIR}/gen-nodejs-es6
${THRIFT_COMPILER} -out ${DIR}/gen-nodejs-es6 --gen js:node,es6 ${THRIFT_FILES_DIR}/v0.16/ThriftTest.thrift
${THRIFT_COMPILER} -out ${DIR}/gen-nodejs-es6 --gen js:node,es6 ${THRIFT_FILES_DIR}/JsDeepConstructorTest.thrift
${THRIFT_COMPILER} -out ${DIR}/gen-nodejs-es6 --gen js:node,es6 ${THRIFT_FILES_DIR}/Int64Test.thrift
+${THRIFT_COMPILER} -out ${DIR}/gen-nodejs-es6 --gen js:node,es6 ${THRIFT_FILES_DIR}/Include.thrift
+mkdir ${DIR}/gen-nodejs-esm
+${THRIFT_COMPILER} -out ${DIR}/gen-nodejs-esm --gen js:node,es6,esm ${THRIFT_FILES_DIR}/v0.16/ThriftTest.thrift
+${THRIFT_COMPILER} -out ${DIR}/gen-nodejs-esm --gen js:node,es6,esm ${THRIFT_FILES_DIR}/JsDeepConstructorTest.thrift
+${THRIFT_COMPILER} -out ${DIR}/gen-nodejs-esm --gen js:node,es6,esm ${THRIFT_FILES_DIR}/Int64Test.thrift
+${THRIFT_COMPILER} -out ${DIR}/gen-nodejs-esm --gen js:node,es6,esm ${THRIFT_FILES_DIR}/Include.thrift
# generate episodic compilation test code
TYPES_PACKAGE=${EPISODIC_DIR}/node_modules/types-package
@@ -121,6 +126,7 @@
node ${DIR}/header.test.js || TESTOK=1
node ${DIR}/int64.test.js || TESTOK=1
node ${DIR}/deep-constructor.test.js || TESTOK=1
+node ${DIR}/include.test.mjs || TESTOK=1
# integration tests
@@ -130,11 +136,11 @@
do
for transport in buffered framed
do
- for ecma_version in es5 es6
+ for gen_variant in es5 es6 esm
do
- testServer $ecma_version $type $protocol $transport || TESTOK=1
- testServer $ecma_version $type $protocol $transport --ssl || TESTOK=1
- testServer $ecma_version $type $protocol $transport --callback || TESTOK=1
+ testServer $gen_variant $type $protocol $transport || TESTOK=1
+ testServer $gen_variant $type $protocol $transport --ssl || TESTOK=1
+ testServer $gen_variant $type $protocol $transport --callback || TESTOK=1
done
done
done
diff --git a/lib/nodejs/test/test_driver.js b/lib/nodejs/test/test_driver.mjs
similarity index 95%
rename from lib/nodejs/test/test_driver.js
rename to lib/nodejs/test/test_driver.mjs
index 0593aea..eca56ba 100644
--- a/lib/nodejs/test/test_driver.js
+++ b/lib/nodejs/test/test_driver.mjs
@@ -26,15 +26,20 @@
// supports an optional callback function which is called with
// a status message when the test is complete.
-const test = require("tape");
+import test from "tape";
-const helpers = require("./helpers");
-const ttypes = require(`./${helpers.genPath}/ThriftTest_types`);
-const TException = require("thrift").Thrift.TException;
-const Int64 = require("node-int64");
-const testCases = require("./test-cases");
+import helpers from "./helpers.js";
+import thrift from "thrift";
+import Int64 from "node-int64";
+import * as testCases from "./test-cases.mjs";
-exports.ThriftTestDriver = function (client, callback) {
+const ttypes = await import(
+ `./${helpers.genPath}/ThriftTest_types.${helpers.moduleExt}`
+);
+
+const TException = thrift.Thrift.TException;
+
+export const ThriftTestDriver = function (client, callback) {
test(
"NodeJS Style Callback Client Tests",
{ skip: helpers.ecmaMode === "es6" },
@@ -162,7 +167,7 @@
}
};
-exports.ThriftTestDriverPromise = function (client, callback) {
+export const ThriftTestDriverPromise = function (client, callback) {
test("Promise Client Tests", function (assert) {
const checkRecursively = makeRecursiveCheck(assert);
diff --git a/lib/nodejs/test/test_handler.js b/lib/nodejs/test/test_handler.mjs
similarity index 95%
rename from lib/nodejs/test/test_handler.js
rename to lib/nodejs/test/test_handler.mjs
index a6a6fc2..a378fe1 100644
--- a/lib/nodejs/test/test_handler.js
+++ b/lib/nodejs/test/test_handler.mjs
@@ -19,9 +19,11 @@
//This is the server side Node test handler for the standard
// Apache Thrift test service.
-const helpers = require("./helpers");
-const ttypes = require(`./${helpers.genPath}/ThriftTest_types`);
-const TException = require("thrift").Thrift.TException;
+import helpers from "./helpers.js";
+import thrift from "thrift";
+
+const ttypes = await helpers.importTypes(`ThriftTest_types`);
+const TException = thrift.Thrift.TException;
function makeSyncHandler() {
return function (thing) {
@@ -217,4 +219,4 @@
asyncHandlers[label] = makeAsyncHandler(label);
});
-exports.ThriftTestHandler = asyncHandlers;
+export { asyncHandlers as ThriftTestHandler };