Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/tools/minimal_runtime_shell.py
4128 views
1
import re
2
import sys
3
import os
4
import logging
5
6
__scriptdir__ = os.path.dirname(os.path.abspath(__file__))
7
__rootdir__ = os.path.dirname(__scriptdir__)
8
sys.path.insert(0, __rootdir__)
9
10
from . import building
11
from . import shared
12
from . import utils
13
from .settings import settings
14
15
logger = logging.getLogger('minimal_runtime_shell')
16
17
18
def generate_minimal_runtime_load_statement(target_basename):
19
# Extra code to appear before the loader
20
prefix_statements = []
21
# Statements to appear inside a Promise .then() block after loading has finished
22
then_statements = []
23
# Import parameters to call the main JS runtime function with
24
modularize_imports = []
25
26
# Depending on whether streaming Wasm compilation is enabled or not, the minimal sized code to
27
# download Wasm looks a bit different.
28
# Expand {{{ DOWNLOAD_WASM }}} block from here (if we added #define support, this could be done in
29
# the template directly)
30
if settings.MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION:
31
if settings.MIN_SAFARI_VERSION < 150000 or settings.MIN_NODE_VERSION < 180100 or settings.MIN_FIREFOX_VERSION < 58 or settings.MIN_CHROME_VERSION < 61:
32
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/compileStreaming
33
download_wasm = f"WebAssembly.compileStreaming ? WebAssembly.compileStreaming(fetch('{target_basename}.wasm')) : binary('{target_basename}.wasm')"
34
else:
35
# WebAssembly.compileStreaming() is unconditionally supported:
36
download_wasm = f"WebAssembly.compileStreaming(fetch('{target_basename}.wasm'))"
37
elif settings.MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION:
38
# Same compatibility story as above for
39
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiateStreaming
40
if settings.MIN_SAFARI_VERSION < 150000 or settings.MIN_NODE_VERSION < 180100 or settings.MIN_FIREFOX_VERSION < 58 or settings.MIN_CHROME_VERSION < 61:
41
download_wasm = f"!WebAssembly.instantiateStreaming && binary('{target_basename}.wasm')"
42
else:
43
# WebAssembly.instantiateStreaming() is unconditionally supported, so we do not download wasm
44
# in the .html file, but leave it to the .js file to download
45
download_wasm = None
46
else:
47
download_wasm = f"binary('{target_basename}.wasm')"
48
49
# Main JS file always in first entry
50
files_to_load = [f"script('{settings.TARGET_JS_NAME}')"]
51
52
# Download .wasm file
53
if (settings.WASM == 1 and settings.WASM2JS == 0) or not download_wasm:
54
if settings.MODULARIZE:
55
modularize_imports += [f'wasm: r[{len(files_to_load)}]']
56
else:
57
then_statements += [f"{settings.EXPORT_NAME}.wasm = r[{len(files_to_load)}];"]
58
if download_wasm:
59
files_to_load += [download_wasm]
60
61
# Download wasm_worker file
62
if settings.WASM_WORKERS and settings.MODULARIZE:
63
modularize_imports += ['js: js']
64
65
# Download Wasm2JS code if target browser does not support WebAssembly
66
if settings.WASM == 2:
67
if settings.MODULARIZE:
68
modularize_imports += [f'wasm: supportsWasm ? r[{len(files_to_load)}] : 0']
69
else:
70
then_statements += [f"if (supportsWasm) {settings.EXPORT_NAME}.wasm = r[{len(files_to_load)}];"]
71
files_to_load += [f"supportsWasm ? {download_wasm} : script('{target_basename}.wasm.js')"]
72
73
# Execute compiled output when building with MODULARIZE
74
if settings.MODULARIZE:
75
modularize_imports = ',\n '.join(modularize_imports)
76
if settings.WASM_WORKERS:
77
then_statements += ['''\
78
// Detour the JS code to a separate variable to avoid instantiating with 'r' array as "this"
79
// directly to avoid strict ECMAScript/Firefox GC problems that cause a leak, see
80
// https://bugzilla.mozilla.org/show_bug.cgi?id=1540101
81
var js = URL.createObjectURL(new Blob([r[0]], { type: \'application/javascript\' }));
82
script(js).then((c) => c({
83
%s
84
}));''' % modularize_imports]
85
else:
86
then_statements += ['''\
87
// Detour the JS code to a separate variable to avoid instantiating with 'r' array as "this"
88
// directly to avoid strict ECMAScript/Firefox GC problems that cause a leak, see
89
// https://bugzilla.mozilla.org/show_bug.cgi?id=1540101
90
var js = r[0];
91
js({
92
%s
93
});''' % modularize_imports]
94
95
binary_xhr = ' var binary = (url) => fetch(url).then((rsp) => rsp.arrayBuffer());'
96
97
script_xhr = '''\
98
function script(url) { // Downloads a script file and adds it to DOM
99
return new Promise((ok, err) => {
100
var s = document.createElement('script');
101
s.src = url;
102
s.onload = () => {
103
#if MODULARIZE
104
#if WASM == 2
105
// In MODULARIZEd WASM==2 builds, we use this same function to download
106
// both .js and .asm.js that are structured with {{{ EXPORT_NAME }}}
107
// at the top level, but also use this function to download the Wasm2JS
108
// file that does not have an {{{ EXPORT_NAME }}} function, hence the
109
// variable typeof check:
110
if (typeof {{{ EXPORT_NAME }}} !== 'undefined') {
111
var c = {{{ EXPORT_NAME }}};
112
delete {{{ EXPORT_NAME }}};
113
ok(c);
114
} else {
115
ok();
116
}
117
#else
118
var c = {{{ EXPORT_NAME }}};
119
delete {{{ EXPORT_NAME }}};
120
ok(c);
121
#endif
122
#else
123
ok();
124
#endif
125
};
126
document.body.appendChild(s);
127
});
128
}
129
'''
130
131
# Only one file to download - no need to use Promise.all()
132
if len(files_to_load) == 1:
133
if settings.MODULARIZE:
134
return script_xhr + files_to_load[0] + ".then(js);"
135
else:
136
return script_xhr + files_to_load[0] + ";"
137
138
if not settings.MODULARIZE or settings.WASM_WORKERS:
139
# If downloading multiple files like .wasm or .mem, those need to be loaded in
140
# before we can add the main runtime script to the DOM, so convert the main .js
141
# script load from direct script() load to a binary() load so we can still
142
# immediately start the download, but can control when we add the script to the
143
# DOM.
144
if settings.PTHREADS or settings.WASM_WORKERS:
145
script_load = "script(url)"
146
else:
147
script_load = "script(url).then(() => URL.revokeObjectURL(url));"
148
149
if settings.WASM_WORKERS:
150
save_js = f'{settings.EXPORT_NAME}.js = '
151
else:
152
save_js = ''
153
154
files_to_load[0] = f"binary('{settings.TARGET_JS_NAME}')"
155
if not settings.MODULARIZE:
156
then_statements += ["var url = %sURL.createObjectURL(new Blob([r[0]], { type: 'application/javascript' }));" % save_js,
157
script_load]
158
159
# Add in binary() XHR loader if used:
160
if any("binary(" in s for s in files_to_load + then_statements):
161
prefix_statements += [binary_xhr]
162
if any("script(" in s for s in files_to_load + then_statements):
163
prefix_statements += [script_xhr]
164
165
# Several files to download, go via Promise.all()
166
load = '\n'.join(prefix_statements)
167
load += "Promise.all([" + ', '.join(files_to_load) + "])"
168
if len(then_statements) > 0:
169
load += '.then((r) => {\n %s\n});' % '\n '.join(then_statements)
170
return load
171
172
173
def generate_minimal_runtime_html(target, options, js_target, target_basename):
174
logger.debug('generating HTML for minimal runtime')
175
shell = utils.read_file(options.shell_path)
176
if settings.SINGLE_FILE:
177
# No extra files needed to download in a SINGLE_FILE build.
178
shell = shell.replace('{{{ DOWNLOAD_JS_AND_WASM_FILES }}}', '')
179
else:
180
shell = shell.replace('{{{ DOWNLOAD_JS_AND_WASM_FILES }}}', generate_minimal_runtime_load_statement(target_basename))
181
182
temp_files = shared.get_temp_files()
183
with temp_files.get_file(suffix='.js') as shell_temp:
184
utils.write_file(shell_temp, shell)
185
shell = building.read_and_preprocess(shell_temp)
186
187
if re.search(r'{{{\s*SCRIPT\s*}}}', shell):
188
shared.exit_with_error('--shell-file "' + options.shell_path + '": MINIMAL_RUNTIME uses a different kind of HTML page shell file than the traditional runtime! Please see $EMSCRIPTEN/src/shell_minimal_runtime.html for a template to use as a basis.')
189
190
shell = shell.replace('{{{ TARGET_BASENAME }}}', settings.TARGET_BASENAME)
191
shell = shell.replace('{{{ EXPORT_NAME }}}', settings.EXPORT_NAME)
192
shell = shell.replace('{{{ TARGET_JS_NAME }}}', settings.TARGET_JS_NAME)
193
194
# In SINGLE_FILE build, embed the main .js file into the .html output
195
if settings.SINGLE_FILE:
196
js_contents = utils.read_file(js_target)
197
utils.delete_file(js_target)
198
else:
199
js_contents = ''
200
shell = shell.replace('{{{ JS_CONTENTS_IN_SINGLE_FILE_BUILD }}}', js_contents)
201
utils.write_file(target, shell, options.output_eol)
202
203