Skip to content

Commit

Permalink
[tsgen] Make commonjs module output compatible with tsc.
Browse files Browse the repository at this point in the history
This enables commonjs modules to be imported into TypeScript using the same
syntax as ESM modules e.g.

`import moduleFactory from './embind_tsgen.js';`
  • Loading branch information
brendandahl committed Nov 21, 2024
1 parent 76ad476 commit 033cd49
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 31 deletions.
48 changes: 27 additions & 21 deletions test/other/embind_tsgen_main.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
// Example TS program that consumes the emscripten-generated module to to
// illustrate how the type definitions are used and test they are workings as
// expected.
import moduleFactory from './embind_tsgen.mjs';
import moduleFactory from './embind_tsgen.js';

const module = await moduleFactory();
// Async IIFE is required for TSC with commonjs modules. This is noted needed
// for ESM top level await can be used.
(async function() {

// Test a few variations of passing value_objects with strings.
module.setValObj({
bar: module.Bar.valueOne,
string: "ABCD",
callback: () => {}
});
const module = await moduleFactory();

module.setValObj({
bar: module.Bar.valueOne,
string: new Int8Array([65, 66, 67, 68]),
callback: () => {}
});
// Test a few variations of passing value_objects with strings.
module.setValObj({
bar: module.Bar.valueOne,
string: "ABCD",
callback: () => {}
});

const valObj = module.getValObj();
// TODO: remove the cast below when better definitions are generated for value
// objects.
const valString : string = valObj.string as string;
module.setValObj({
bar: module.Bar.valueOne,
string: new Int8Array([65, 66, 67, 68]),
callback: () => {}
});

// Ensure nonnull pointers do no need a cast or nullptr check to use.
const obj = module.getNonnullPointer();
obj.delete();
const valObj = module.getValObj();
// TODO: remove the cast below when better definitions are generated for value
// objects.
const valString : string = valObj.string as string;

console.log('ts ran');
// Ensure nonnull pointers do no need a cast or nullptr check to use.
const obj = module.getNonnullPointer();
obj.delete();

console.log('ts ran');

})();
19 changes: 11 additions & 8 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -3385,21 +3385,24 @@ def test_jspi_add_function(self):
self.do_runf('other/test_jspi_add_function.c', 'done')

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

# Test that the output compiles with a TS file that uses the defintions.
shutil.copyfile(test_file('other/embind_tsgen_main.ts'), 'main.ts')
# A package file with type=module is needed to enabled ES modules in TSC and
# also run the output JS file as a module in node.
shutil.copyfile(test_file('other/embind_tsgen_package.json'), 'package.json')
cmd = shared.get_npm_cmd('tsc') + ['embind_tsgen.d.ts', 'main.ts', '--module', 'NodeNext', '--moduleResolution', 'nodenext']
if '-sEXPORT_ES6' in opts:
# A package file with type=module is needed to enabled ES modules in TSC and
# also run the output JS file as a module in node.
shutil.copyfile(test_file('other/embind_tsgen_package.json'), 'package.json')

cmd = shared.get_npm_cmd('tsc') + ['embind_tsgen.d.ts', 'main.ts', '--target', 'es2021'] + tsc_opts
shared.check_call(cmd)
actual = read_file('embind_tsgen.d.ts')
self.assertFileContents(test_file('other/embind_tsgen_module.d.ts'), actual)
Expand Down
6 changes: 4 additions & 2 deletions tools/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -2491,9 +2491,11 @@ def modularize():
src += 'export default %s;\n' % settings.EXPORT_NAME
elif not settings.MINIMAL_RUNTIME:
src += '''\
if (typeof exports === 'object' && typeof module === 'object')
if (typeof exports === 'object' && typeof module === 'object') {
module.exports = %(EXPORT_NAME)s;
else if (typeof define === 'function' && define['amd'])
// This default export allows TS to import this commonjs style module.
module.exports.default = %(EXPORT_NAME)s;
} else if (typeof define === 'function' && define['amd'])
define([], () => %(EXPORT_NAME)s);
''' % {'EXPORT_NAME': settings.EXPORT_NAME}

Expand Down

0 comments on commit 033cd49

Please sign in to comment.