Path: blob/main/tools/minimal_runtime_shell.py
6170 views
import logging1import os2import re3import sys45__scriptdir__ = os.path.dirname(os.path.abspath(__file__))6__rootdir__ = os.path.dirname(__scriptdir__)7sys.path.insert(0, __rootdir__)89from . import building, shared, utils10from .settings import settings1112logger = logging.getLogger('minimal_runtime_shell')131415def generate_minimal_runtime_load_statement(target_basename):16# Extra code to appear before the loader17prefix_statements = []18# Statements to appear inside a Promise .then() block after loading has finished19then_statements = []20# Import parameters to call the main JS runtime function with21modularize_imports = []2223# Depending on whether streaming Wasm compilation is enabled or not, the minimal sized code to24# download Wasm looks a bit different.25# Expand {{{ DOWNLOAD_WASM }}} block from here (if we added #define support, this could be done in26# the template directly)27if settings.MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION:28if settings.MIN_SAFARI_VERSION < 150000 or settings.MIN_NODE_VERSION < 180100 or settings.MIN_FIREFOX_VERSION < 58:29# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/compileStreaming30download_wasm = f"WebAssembly.compileStreaming ? WebAssembly.compileStreaming(fetch('{target_basename}.wasm')) : binary('{target_basename}.wasm')"31else:32# WebAssembly.compileStreaming() is unconditionally supported:33download_wasm = f"WebAssembly.compileStreaming(fetch('{target_basename}.wasm'))"34elif settings.MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION:35# Same compatibility story as above for36# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiateStreaming37if settings.MIN_SAFARI_VERSION < 150000 or settings.MIN_NODE_VERSION < 180100 or settings.MIN_FIREFOX_VERSION < 58:38download_wasm = f"!WebAssembly.instantiateStreaming && binary('{target_basename}.wasm')"39else:40# WebAssembly.instantiateStreaming() is unconditionally supported, so we do not download wasm41# in the .html file, but leave it to the .js file to download42download_wasm = None43else:44download_wasm = f"binary('{target_basename}.wasm')"4546# Main JS file always in first entry47files_to_load = [f"script('{settings.TARGET_JS_NAME}')"]4849# Download .wasm file50if (settings.WASM == 1 and settings.WASM2JS == 0) or not download_wasm:51if settings.MODULARIZE:52modularize_imports += [f'wasm: r[{len(files_to_load)}]']53else:54then_statements += [f"{settings.EXPORT_NAME}.wasm = r[{len(files_to_load)}];"]55if download_wasm:56files_to_load += [download_wasm]5758# Download wasm_worker file59if settings.WASM_WORKERS and settings.MODULARIZE:60modularize_imports += ['js: js']6162# Download Wasm2JS code if target browser does not support WebAssembly63if settings.WASM == 2:64if settings.MODULARIZE:65modularize_imports += [f'wasm: supportsWasm ? r[{len(files_to_load)}] : 0']66else:67then_statements += [f"if (supportsWasm) {settings.EXPORT_NAME}.wasm = r[{len(files_to_load)}];"]68files_to_load += [f"supportsWasm ? {download_wasm} : script('{target_basename}.wasm.js')"]6970# Execute compiled output when building with MODULARIZE71if settings.MODULARIZE:72modularize_imports = ',\n '.join(modularize_imports)73if settings.WASM_WORKERS:74then_statements += ['''\75// Detour the JS code to a separate variable to avoid instantiating with 'r' array as "this"76// directly to avoid strict ECMAScript/Firefox GC problems that cause a leak, see77// https://bugzil.la/154010178var js = URL.createObjectURL(new Blob([r[0]], { type: \'application/javascript\' }));79script(js).then((c) => c({80%s81}));''' % modularize_imports]82else:83then_statements += ['''\84// Detour the JS code to a separate variable to avoid instantiating with 'r' array as "this"85// directly to avoid strict ECMAScript/Firefox GC problems that cause a leak, see86// https://bugzil.la/154010187var js = r[0];88js({89%s90});''' % modularize_imports]9192binary_xhr = ' var binary = (url) => fetch(url).then((rsp) => rsp.arrayBuffer());'9394script_xhr = '''\95function script(url) { // Downloads a script file and adds it to DOM96return new Promise((ok) => {97var s = document.createElement('script');98s.src = url;99s.onload = () => {100#if MODULARIZE101#if WASM == 2102// In MODULARIZEd WASM==2 builds, we use this same function to download103// both .js and .asm.js that are structured with {{{ EXPORT_NAME }}}104// at the top level, but also use this function to download the Wasm2JS105// file that does not have an {{{ EXPORT_NAME }}} function, hence the106// variable typeof check:107if (typeof {{{ EXPORT_NAME }}} !== 'undefined') {108var c = {{{ EXPORT_NAME }}};109delete {{{ EXPORT_NAME }}};110ok(c);111} else {112ok();113}114#else115var c = {{{ EXPORT_NAME }}};116delete {{{ EXPORT_NAME }}};117ok(c);118#endif119#else120ok();121#endif122};123document.body.appendChild(s);124});125}126'''127128# Only one file to download - no need to use Promise.all()129if len(files_to_load) == 1:130if settings.MODULARIZE:131return script_xhr + files_to_load[0] + ".then(js);"132else:133return script_xhr + files_to_load[0] + ";"134135if not settings.MODULARIZE or settings.WASM_WORKERS:136# If downloading multiple files like .wasm or .mem, those need to be loaded in137# before we can add the main runtime script to the DOM, so convert the main .js138# script load from direct script() load to a binary() load so we can still139# immediately start the download, but can control when we add the script to the140# DOM.141if settings.PTHREADS or settings.WASM_WORKERS:142script_load = "script(url)"143else:144script_load = "script(url).then(() => URL.revokeObjectURL(url));"145146if settings.WASM_WORKERS:147save_js = f'{settings.EXPORT_NAME}.js = '148else:149save_js = ''150151files_to_load[0] = f"binary('{settings.TARGET_JS_NAME}')"152if not settings.MODULARIZE:153then_statements += ["var url = %sURL.createObjectURL(new Blob([r[0]], { type: 'application/javascript' }));" % save_js,154script_load]155156# Add in binary() XHR loader if used:157if any("binary(" in s for s in files_to_load + then_statements):158prefix_statements += [binary_xhr]159if any("script(" in s for s in files_to_load + then_statements):160prefix_statements += [script_xhr]161162# Several files to download, go via Promise.all()163load = '\n'.join(prefix_statements)164load += "Promise.all([" + ', '.join(files_to_load) + "])"165if len(then_statements) > 0:166load += '.then((r) => {\n %s\n});' % '\n '.join(then_statements)167return load168169170def generate_minimal_runtime_html(target, options, js_target, target_basename):171logger.debug('generating HTML for minimal runtime')172shell = utils.read_file(options.shell_html)173if settings.SINGLE_FILE:174# No extra files needed to download in a SINGLE_FILE build.175shell = shell.replace('{{{ DOWNLOAD_JS_AND_WASM_FILES }}}', '')176else:177shell = shell.replace('{{{ DOWNLOAD_JS_AND_WASM_FILES }}}', generate_minimal_runtime_load_statement(target_basename))178179temp_files = shared.get_temp_files()180with temp_files.get_file(suffix='.js') as shell_temp:181utils.write_file(shell_temp, shell)182shell = building.read_and_preprocess(shell_temp)183184if re.search(r'{{{\s*SCRIPT\s*}}}', shell):185utils.exit_with_error(f'--shell-file "{options.shell_html}": MINIMAL_RUNTIME uses a different kind of HTML page shell file than the traditional runtime! Please see $EMSCRIPTEN/html/shell_minimal_runtime.html for a template to use as a basis.')186187shell = shell.replace('{{{ TARGET_BASENAME }}}', settings.TARGET_BASENAME)188shell = shell.replace('{{{ EXPORT_NAME }}}', settings.EXPORT_NAME)189shell = shell.replace('{{{ TARGET_JS_NAME }}}', settings.TARGET_JS_NAME)190191# In SINGLE_FILE build, embed the main .js file into the .html output192if settings.SINGLE_FILE:193js_contents = utils.read_file(js_target)194utils.delete_file(js_target)195else:196js_contents = ''197shell = shell.replace('{{{ JS_CONTENTS_IN_SINGLE_FILE_BUILD }}}', js_contents)198utils.write_file(target, shell, options.output_eol)199200201