Skip to content

Commit bb2a7f0

Browse files
authored
[tsgen] Make commonjs module output compatible with tsc. (#22988)
This enables commonjs modules to be imported into TypeScript using the same syntax as ESM modules e.g. `import moduleFactory from './embind_tsgen.js';`
1 parent d833c39 commit bb2a7f0

File tree

3 files changed

+46
-31
lines changed

3 files changed

+46
-31
lines changed

test/other/embind_tsgen_main.ts

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,39 @@
11
// Example TS program that consumes the emscripten-generated module to to
22
// illustrate how the type definitions are used and test they are workings as
33
// expected.
4-
import moduleFactory from './embind_tsgen.mjs';
54

6-
const module = await moduleFactory();
5+
// The imported file will either be an ES module or a CommonJS module depending
6+
// on the test.
7+
import moduleFactory from './embind_tsgen.js';
78

8-
// Test a few variations of passing value_objects with strings.
9-
module.setValObj({
10-
bar: module.Bar.valueOne,
11-
string: "ABCD",
12-
callback: () => {}
13-
});
9+
// Async IIFE is required for TSC with commonjs modules. This is not needed for
10+
// ESM output since top level await can be used.
11+
(async function() {
1412

15-
module.setValObj({
16-
bar: module.Bar.valueOne,
17-
string: new Int8Array([65, 66, 67, 68]),
18-
callback: () => {}
19-
});
13+
const module = await moduleFactory();
2014

21-
const valObj = module.getValObj();
22-
// TODO: remove the cast below when better definitions are generated for value
23-
// objects.
24-
const valString : string = valObj.string as string;
15+
// Test a few variations of passing value_objects with strings.
16+
module.setValObj({
17+
bar: module.Bar.valueOne,
18+
string: "ABCD",
19+
callback: () => {}
20+
});
2521

26-
// Ensure nonnull pointers do no need a cast or nullptr check to use.
27-
const obj = module.getNonnullPointer();
28-
obj.delete();
22+
module.setValObj({
23+
bar: module.Bar.valueOne,
24+
string: new Int8Array([65, 66, 67, 68]),
25+
callback: () => {}
26+
});
2927

30-
console.log('ts ran');
28+
const valObj = module.getValObj();
29+
// TODO: remove the cast below when better definitions are generated for value
30+
// objects.
31+
const valString : string = valObj.string as string;
32+
33+
// Ensure nonnull pointers do no need a cast or nullptr check to use.
34+
const obj = module.getNonnullPointer();
35+
obj.delete();
36+
37+
console.log('ts ran');
38+
39+
})();

test/test_other.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3397,21 +3397,24 @@ def test_jspi_add_function(self):
33973397
self.do_runf('other/test_jspi_add_function.c', 'done')
33983398

33993399
@parameterized({
3400-
'': [[]],
3401-
'with_jsgen': [['-sEMBIND_AOT']]
3400+
'commonjs': [['-sMODULARIZE'], ['--module', 'commonjs', '--moduleResolution', 'node']],
3401+
'esm': [['-sEXPORT_ES6'], ['--module', 'NodeNext', '--moduleResolution', 'nodenext']],
3402+
'esm_with_jsgen': [['-sEXPORT_ES6', '-sEMBIND_AOT'], ['--module', 'NodeNext', '--moduleResolution', 'nodenext']]
34023403
})
3403-
def test_embind_tsgen(self, opts):
3404+
def test_embind_tsgen_end_to_end(self, opts, tsc_opts):
34043405
# Check that TypeScript generation works and that the program is runs as
34053406
# expected.
34063407
self.emcc(test_file('other/embind_tsgen.cpp'),
3407-
['-o', 'embind_tsgen.mjs', '-lembind', '--emit-tsd', 'embind_tsgen.d.ts'] + opts)
3408+
['-o', 'embind_tsgen.js', '-lembind', '--emit-tsd', 'embind_tsgen.d.ts'] + opts)
34083409

34093410
# Test that the output compiles with a TS file that uses the defintions.
34103411
shutil.copyfile(test_file('other/embind_tsgen_main.ts'), 'main.ts')
3411-
# A package file with type=module is needed to enabled ES modules in TSC and
3412-
# also run the output JS file as a module in node.
3413-
shutil.copyfile(test_file('other/embind_tsgen_package.json'), 'package.json')
3414-
cmd = shared.get_npm_cmd('tsc') + ['embind_tsgen.d.ts', 'main.ts', '--module', 'NodeNext', '--moduleResolution', 'nodenext']
3412+
if '-sEXPORT_ES6' in opts:
3413+
# A package file with type=module is needed to enabled ES modules in TSC and
3414+
# also run the output JS file as a module in node.
3415+
shutil.copyfile(test_file('other/embind_tsgen_package.json'), 'package.json')
3416+
3417+
cmd = shared.get_npm_cmd('tsc') + ['embind_tsgen.d.ts', 'main.ts', '--target', 'es2021'] + tsc_opts
34153418
shared.check_call(cmd)
34163419
actual = read_file('embind_tsgen.d.ts')
34173420
self.assertFileContents(test_file('other/embind_tsgen_module.d.ts'), actual)

tools/link.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2486,9 +2486,12 @@ def modularize():
24862486
src += 'export default %s;\n' % settings.EXPORT_NAME
24872487
elif not settings.MINIMAL_RUNTIME:
24882488
src += '''\
2489-
if (typeof exports === 'object' && typeof module === 'object')
2489+
if (typeof exports === 'object' && typeof module === 'object') {
24902490
module.exports = %(EXPORT_NAME)s;
2491-
else if (typeof define === 'function' && define['amd'])
2491+
// This default export looks redundant, but it allows TS to import this
2492+
// commonjs style module.
2493+
module.exports.default = %(EXPORT_NAME)s;
2494+
} else if (typeof define === 'function' && define['amd'])
24922495
define([], () => %(EXPORT_NAME)s);
24932496
''' % {'EXPORT_NAME': settings.EXPORT_NAME}
24942497

0 commit comments

Comments
 (0)