Skip to content

Commit 59c47d2

Browse files
authored
[file_packager.py] Add --export-es6 flag to file_packager.py (#24737)
Fixes: #24504
1 parent 319a76c commit 59c47d2

File tree

3 files changed

+106
-20
lines changed

3 files changed

+106
-20
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: 36 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,37 @@ 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+
var module = await loadModule();
3978+
await loadDataFile(module);
3979+
module._test_fun();
3980+
''')
3981+
3982+
self.assertContained('hello data', self.run_js('run.js'))
3983+
39483984
@crossplatform
39493985
def test_file_packager_depfile(self):
39503986
create_file('data1.txt', 'data1')

tools/file_packager.py

Lines changed: 68 additions & 20 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 = '''
@@ -688,6 +717,10 @@ def generate_js(data_target, data_files, metadata):
688717
Module['FS_createDataFile'](this.name, null, byteArray, true, true, true);
689718
Module['removeRunDependency'](`fp ${that.name}`);'''
690719

720+
finish_handler = create_preloaded if options.use_preload_plugins else create_data
721+
if options.export_es6:
722+
finish_handler += '\nloadDataResolve();'
723+
691724
if not options.lz4:
692725
# Data requests - for getting a block of data out of the big archive - have
693726
# a similar API to XHRs
@@ -720,11 +753,18 @@ def generate_js(data_target, data_files, metadata):
720753
var files = metadata['files'];
721754
for (var i = 0; i < files.length; ++i) {
722755
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)
756+
}\n''' % finish_handler
724757

725758
if options.has_embedded and not options.obj_output:
726759
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')
727760

761+
catch_handler = ''
762+
if options.export_es6:
763+
catch_handler += '''
764+
.catch((error) => {
765+
loadDataReject(error);
766+
})'''
767+
728768
for counter, file_ in enumerate(data_files):
729769
filename = file_.dstpath
730770
dirname = os.path.dirname(filename)
@@ -1049,10 +1089,10 @@ def generate_js(data_target, data_files, metadata):
10491089
}
10501090
}
10511091
} catch(e) {
1052-
await preloadFallback(e);
1092+
await preloadFallback(e)%s;
10531093
}
10541094
1055-
Module['setStatus']?.('Downloading...');\n'''
1095+
Module['setStatus']?.('Downloading...');\n''' % catch_handler
10561096
else:
10571097
# Not using preload cache, so we might as well start the xhr ASAP,
10581098
# potentially before JS parsing of the main codebase if it's after us.
@@ -1065,15 +1105,16 @@ def generate_js(data_target, data_files, metadata):
10651105
if (!fetched) {
10661106
// Note that we don't use await here because we want to execute the
10671107
// the rest of this function immediately.
1068-
fetchRemotePackage(REMOTE_PACKAGE_NAME, REMOTE_PACKAGE_SIZE).then((data) => {
1069-
if (fetchedCallback) {
1070-
fetchedCallback(data);
1071-
fetchedCallback = null;
1072-
} else {
1073-
fetched = data;
1074-
}
1075-
})
1076-
}\n'''
1108+
fetchRemotePackage(REMOTE_PACKAGE_NAME, REMOTE_PACKAGE_SIZE)
1109+
.then((data) => {
1110+
if (fetchedCallback) {
1111+
fetchedCallback(data);
1112+
fetchedCallback = null;
1113+
} else {
1114+
fetched = data;
1115+
}
1116+
})%s;
1117+
}\n''' % catch_handler
10771118

10781119
code += '''
10791120
Module['preloadResults'][PACKAGE_NAME] = {fromCache: false};
@@ -1090,10 +1131,10 @@ def generate_js(data_target, data_files, metadata):
10901131
ret += '''
10911132
}
10921133
if (Module['calledRun']) {
1093-
runWithFS(Module);
1134+
runWithFS(Module)%s;
10941135
} else {
10951136
(Module['preRun'] ??= []).push(runWithFS); // FS is not initialized yet, wait for it
1096-
}\n'''
1137+
}\n''' % catch_handler
10971138

10981139
if options.separate_metadata:
10991140
node_support_code = ''
@@ -1131,7 +1172,14 @@ def generate_js(data_target, data_files, metadata):
11311172
}
11321173
loadPackage(%s);\n''' % json.dumps(metadata)
11331174

1134-
ret += '''
1175+
if options.export_es6:
1176+
ret += '''
1177+
});
1178+
}
1179+
// END the loadDataFile function
1180+
'''
1181+
else:
1182+
ret += '''
11351183
})();\n'''
11361184

11371185
return ret

0 commit comments

Comments
 (0)