Skip to content

Commit 115e93d

Browse files
committed
Add modularize to file_packager.py
fix python format apply more ruff fixes fix pipeline revert file to the most actual version and apply changes add modularize to options class add utilization of EXPORT_NAME with modularize option simplify declaration of the modularize function arg documentation address @sbc100 review Fix codegen new line formatting Add file_packager tests and changelog mention fix some stylistic issues gichange file_packager modularize test simplyfi tests fix test rename to export_es6 async function promise behaviour cleanup test address @sbc100 review simplify the test rename promise handlers introduce createRequire in es6 mode alternative way to import require
1 parent 319a76c commit 115e93d

File tree

3 files changed

+93
-8
lines changed

3 files changed

+93
-8
lines changed

ChangeLog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ See docs/process.md for more on how version tagging works.
4545
- emcc will now error if `MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION` or
4646
`MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION` are used with `SINGLE_FILE`.
4747
These are fundamentally incompatible but were previously ignored. (#24849)
48+
- `--export-es6` flag was added to `file_packager.py` available when run
49+
standalone, to enable ES6 imports of generated JavaScript code (#24737)
4850

4951
4.0.12 - 08/01/25
5052
-----------------

test/test_other.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3919,6 +3919,11 @@ def test_file_packager_returns_error_if_target_equal_to_jsoutput(self):
39193919
err = self.expect_fail([FILE_PACKAGER, 'test.data', '--js-output=test.data'])
39203920
self.assertContained(MESSAGE, err)
39213921

3922+
def test_file_packager_returns_error_if_emcc_and_export_es6(self):
3923+
MESSAGE = 'error: Can\'t use --export-es6 option together with --from-emcc since the code should be embedded within emcc\'s code'
3924+
err = self.expect_fail([FILE_PACKAGER, 'test.data', '--export-es6', '--from-emcc'])
3925+
self.assertContained(MESSAGE, err)
3926+
39223927
def test_file_packager_embed(self):
39233928
create_file('data.txt', 'hello data')
39243929

@@ -3945,6 +3950,43 @@ def test_file_packager_embed(self):
39453950
output = self.run_js('a.out.js')
39463951
self.assertContained('hello data', output)
39473952

3953+
def test_file_packager_export_es6(self):
3954+
create_file('smth.txt', 'hello data')
3955+
self.run_process([FILE_PACKAGER, 'test.data', '--export-es6', '--preload', 'smth.txt', '--js-output=dataFileLoader.js'])
3956+
3957+
create_file('test.c', '''
3958+
#include <stdio.h>
3959+
#include <emscripten.h>
3960+
3961+
EMSCRIPTEN_KEEPALIVE int test_fun() {
3962+
FILE* f = fopen("smth.txt", "r");
3963+
char buf[64] = {0};
3964+
int rtn = fread(buf, 1, 64, f);
3965+
buf[rtn] = '\\0';
3966+
fclose(f);
3967+
printf("%s\\n", buf);
3968+
return 0;
3969+
}
3970+
''')
3971+
self.run_process([EMCC, 'test.c', '-sFORCE_FILESYSTEM', '-sMODULARIZE', '-sEXPORT_ES6', '-o', 'moduleFile.js'])
3972+
3973+
create_file('run.js', '''
3974+
import loadDataFile from './dataFileLoader.js'
3975+
import {default as loadModule} from './moduleFile.js'
3976+
3977+
loadModule().then((module) => {
3978+
loadDataFile(module)
3979+
.catch((cause) => Promise.reject(cause))
3980+
.then(() => {
3981+
module._test_fun();
3982+
}
3983+
);
3984+
});
3985+
''')
3986+
3987+
output = self.run_js('run.js')
3988+
self.assertContained('hello data', output)
3989+
39483990
@crossplatform
39493991
def test_file_packager_depfile(self):
39503992
create_file('data1.txt', 'data1')

tools/file_packager.py

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
2222
Usage:
2323
24-
file_packager TARGET [--preload A [B..]] [--embed C [D..]] [--exclude E [F..]] [--js-output=OUTPUT.js] [--no-force] [--use-preload-cache] [--indexedDB-name=EM_PRELOAD_CACHE] [--separate-metadata] [--lz4] [--use-preload-plugins] [--no-node] [--help]
24+
file_packager TARGET [--preload A [B..]] [--embed C [D..]] [--exclude E [F..]] [--js-output=OUTPUT.js] [--no-force] [--use-preload-cache] [--indexedDB-name=EM_PRELOAD_CACHE] [--separate-metadata] [--lz4] [--use-preload-plugins] [--no-node] [--export-es6] [--help]
2525
2626
--preload ,
2727
--embed See emcc --help for more details on those options.
@@ -41,6 +41,8 @@
4141
4242
--export-name=EXPORT_NAME Use custom export name (default is `Module`)
4343
44+
--export-es6 Wrap generated code inside ES6 exported function
45+
4446
--no-force Don't create output if no valid input file is specified.
4547
4648
--use-preload-cache Stores package in IndexedDB so that subsequent loads don't need to do XHR. Checks package version.
@@ -129,6 +131,7 @@ def __init__(self):
129131
self.use_preload_plugins = False
130132
self.support_node = True
131133
self.wasm64 = False
134+
self.export_es6 = False
132135

133136

134137
class DataFile:
@@ -362,7 +365,7 @@ def main(): # noqa: C901, PLR0912, PLR0915
362365
To revalidate these numbers, run `ruff check --select=C901,PLR091`.
363366
"""
364367
if len(sys.argv) == 1:
365-
err('''Usage: file_packager TARGET [--preload A [B..]] [--embed C [D..]] [--exclude E [F..]] [--js-output=OUTPUT.js] [--no-force] [--use-preload-cache] [--indexedDB-name=EM_PRELOAD_CACHE] [--separate-metadata] [--lz4] [--use-preload-plugins] [--no-node] [--help]
368+
err('''Usage: file_packager TARGET [--preload A [B..]] [--embed C [D..]] [--exclude E [F..]] [--js-output=OUTPUT.js] [--no-force] [--use-preload-cache] [--indexedDB-name=EM_PRELOAD_CACHE] [--separate-metadata] [--lz4] [--use-preload-plugins] [--no-node] [--export-es6] [--help]
366369
Try 'file_packager --help' for more details.''')
367370
return 1
368371

@@ -391,6 +394,9 @@ def main(): # noqa: C901, PLR0912, PLR0915
391394
elif arg == '--no-force':
392395
options.force = False
393396
leading = ''
397+
elif arg == '--export-es6':
398+
options.export_es6 = True
399+
leading = ''
394400
elif arg == '--use-preload-cache':
395401
options.use_preload_cache = True
396402
leading = ''
@@ -485,6 +491,11 @@ def main(): # noqa: C901, PLR0912, PLR0915
485491
diagnostics.error('TARGET should not be the same value of --js-output')
486492
return 1
487493

494+
if options.from_emcc and options.export_es6:
495+
diagnostics.error('Can\'t use --export-es6 option together with --from-emcc since the code should be embedded '
496+
'within emcc\'s code')
497+
return 1
498+
488499
walked.append(__file__)
489500
for file_ in data_files:
490501
if not should_ignore(file_.srcpath):
@@ -621,20 +632,38 @@ def generate_js(data_target, data_files, metadata):
621632
if options.from_emcc:
622633
ret = ''
623634
else:
624-
ret = '''
635+
if options.export_es6:
636+
ret = 'export default async function loadDataFile(Module) {\n'
637+
else:
638+
ret = '''
625639
var Module = typeof %(EXPORT_NAME)s != 'undefined' ? %(EXPORT_NAME)s : {};\n''' % {"EXPORT_NAME": options.export_name}
626640

627641
ret += '''
628642
Module['expectedDataFileDownloads'] ??= 0;
629-
Module['expectedDataFileDownloads']++;
630-
(() => {
643+
Module['expectedDataFileDownloads']++;'''
644+
645+
if not options.export_es6:
646+
ret += '''
647+
(() => {'''
648+
649+
ret += '''
631650
// Do not attempt to redownload the virtual filesystem data when in a pthread or a Wasm Worker context.
632651
var isPthread = typeof ENVIRONMENT_IS_PTHREAD != 'undefined' && ENVIRONMENT_IS_PTHREAD;
633652
var isWasmWorker = typeof ENVIRONMENT_IS_WASM_WORKER != 'undefined' && ENVIRONMENT_IS_WASM_WORKER;
634653
if (isPthread || isWasmWorker) return;\n'''
635654

636655
if options.support_node:
637656
ret += " var isNode = typeof process === 'object' && typeof process.versions === 'object' && typeof process.versions.node === 'string';\n"
657+
658+
if options.support_node and options.export_es6:
659+
ret += '''if (isNode) {
660+
const { createRequire } = await import('module');
661+
/** @suppress{duplicate} */
662+
var require = createRequire(import.meta.url);
663+
}\n'''
664+
665+
if options.export_es6:
666+
ret += 'return new Promise((loadDataResolve, loadDataReject) => {\n'
638667
ret += ' async function loadPackage(metadata) {\n'
639668

640669
code = '''
@@ -687,6 +716,8 @@ def generate_js(data_target, data_files, metadata):
687716
create_data = '''// canOwn this data in the filesystem, it is a slice into the heap that will never change
688717
Module['FS_createDataFile'](this.name, null, byteArray, true, true, true);
689718
Module['removeRunDependency'](`fp ${that.name}`);'''
719+
ready_promise = '''
720+
loadDataResolve();'''
690721

691722
if not options.lz4:
692723
# Data requests - for getting a block of data out of the big archive - have
@@ -713,14 +744,14 @@ def generate_js(data_target, data_files, metadata):
713744
finish: async function(byteArray) {
714745
var that = this;
715746
%s
716-
this.requests[this.name] = null;
747+
this.requests[this.name] = null;%s
717748
}
718749
};
719750
720751
var files = metadata['files'];
721752
for (var i = 0; i < files.length; ++i) {
722753
new DataRequest(files[i]['start'], files[i]['end'], files[i]['audio'] || 0).open('GET', files[i]['filename']);
723-
}\n''' % (create_preloaded if options.use_preload_plugins else create_data)
754+
}\n''' % (create_preloaded if options.use_preload_plugins else create_data, ready_promise if options.export_es6 else '')
724755

725756
if options.has_embedded and not options.obj_output:
726757
diagnostics.warn('--obj-output is recommended when using --embed. This outputs an object file for linking directly into your application is more efficient than JS encoding')
@@ -961,6 +992,9 @@ def generate_js(data_target, data_files, metadata):
961992
return contents.buffer;
962993
}'''.strip()
963994

995+
reject_promise = '''
996+
loadDataReject();'''
997+
964998
ret += '''
965999
async function fetchRemotePackage(packageName, packageSize) {
9661000
%(node_support_code)s
@@ -1131,7 +1165,14 @@ def generate_js(data_target, data_files, metadata):
11311165
}
11321166
loadPackage(%s);\n''' % json.dumps(metadata)
11331167

1134-
ret += '''
1168+
if options.export_es6:
1169+
ret += '''
1170+
});
1171+
}
1172+
// END the loadDataFile function
1173+
'''
1174+
else:
1175+
ret += '''
11351176
})();\n'''
11361177

11371178
return ret

0 commit comments

Comments
 (0)