Path: blob/main/tools/minimal_runtime_shell.py
4128 views
import re1import sys2import os3import logging45__scriptdir__ = os.path.dirname(os.path.abspath(__file__))6__rootdir__ = os.path.dirname(__scriptdir__)7sys.path.insert(0, __rootdir__)89from . import building10from . import shared11from . import utils12from .settings import settings1314logger = logging.getLogger('minimal_runtime_shell')151617def generate_minimal_runtime_load_statement(target_basename):18# Extra code to appear before the loader19prefix_statements = []20# Statements to appear inside a Promise .then() block after loading has finished21then_statements = []22# Import parameters to call the main JS runtime function with23modularize_imports = []2425# Depending on whether streaming Wasm compilation is enabled or not, the minimal sized code to26# download Wasm looks a bit different.27# Expand {{{ DOWNLOAD_WASM }}} block from here (if we added #define support, this could be done in28# the template directly)29if settings.MINIMAL_RUNTIME_STREAMING_WASM_COMPILATION:30if settings.MIN_SAFARI_VERSION < 150000 or settings.MIN_NODE_VERSION < 180100 or settings.MIN_FIREFOX_VERSION < 58 or settings.MIN_CHROME_VERSION < 61:31# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/compileStreaming32download_wasm = f"WebAssembly.compileStreaming ? WebAssembly.compileStreaming(fetch('{target_basename}.wasm')) : binary('{target_basename}.wasm')"33else:34# WebAssembly.compileStreaming() is unconditionally supported:35download_wasm = f"WebAssembly.compileStreaming(fetch('{target_basename}.wasm'))"36elif settings.MINIMAL_RUNTIME_STREAMING_WASM_INSTANTIATION:37# Same compatibility story as above for38# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiateStreaming39if settings.MIN_SAFARI_VERSION < 150000 or settings.MIN_NODE_VERSION < 180100 or settings.MIN_FIREFOX_VERSION < 58 or settings.MIN_CHROME_VERSION < 61:40download_wasm = f"!WebAssembly.instantiateStreaming && binary('{target_basename}.wasm')"41else:42# WebAssembly.instantiateStreaming() is unconditionally supported, so we do not download wasm43# in the .html file, but leave it to the .js file to download44download_wasm = None45else:46download_wasm = f"binary('{target_basename}.wasm')"4748# Main JS file always in first entry49files_to_load = [f"script('{settings.TARGET_JS_NAME}')"]5051# Download .wasm file52if (settings.WASM == 1 and settings.WASM2JS == 0) or not download_wasm:53if settings.MODULARIZE:54modularize_imports += [f'wasm: r[{len(files_to_load)}]']55else:56then_statements += [f"{settings.EXPORT_NAME}.wasm = r[{len(files_to_load)}];"]57if download_wasm:58files_to_load += [download_wasm]5960# Download wasm_worker file61if settings.WASM_WORKERS and settings.MODULARIZE:62modularize_imports += ['js: js']6364# Download Wasm2JS code if target browser does not support WebAssembly65if settings.WASM == 2:66if settings.MODULARIZE:67modularize_imports += [f'wasm: supportsWasm ? r[{len(files_to_load)}] : 0']68else:69then_statements += [f"if (supportsWasm) {settings.EXPORT_NAME}.wasm = r[{len(files_to_load)}];"]70files_to_load += [f"supportsWasm ? {download_wasm} : script('{target_basename}.wasm.js')"]7172# Execute compiled output when building with MODULARIZE73if settings.MODULARIZE:74modularize_imports = ',\n '.join(modularize_imports)75if settings.WASM_WORKERS:76then_statements += ['''\77// Detour the JS code to a separate variable to avoid instantiating with 'r' array as "this"78// directly to avoid strict ECMAScript/Firefox GC problems that cause a leak, see79// https://bugzilla.mozilla.org/show_bug.cgi?id=154010180var js = URL.createObjectURL(new Blob([r[0]], { type: \'application/javascript\' }));81script(js).then((c) => c({82%s83}));''' % modularize_imports]84else:85then_statements += ['''\86// Detour the JS code to a separate variable to avoid instantiating with 'r' array as "this"87// directly to avoid strict ECMAScript/Firefox GC problems that cause a leak, see88// https://bugzilla.mozilla.org/show_bug.cgi?id=154010189var js = r[0];90js({91%s92});''' % modularize_imports]9394binary_xhr = ' var binary = (url) => fetch(url).then((rsp) => rsp.arrayBuffer());'9596script_xhr = '''\97function script(url) { // Downloads a script file and adds it to DOM98return new Promise((ok, err) => {99var s = document.createElement('script');100s.src = url;101s.onload = () => {102#if MODULARIZE103#if WASM == 2104// In MODULARIZEd WASM==2 builds, we use this same function to download105// both .js and .asm.js that are structured with {{{ EXPORT_NAME }}}106// at the top level, but also use this function to download the Wasm2JS107// file that does not have an {{{ EXPORT_NAME }}} function, hence the108// variable typeof check:109if (typeof {{{ EXPORT_NAME }}} !== 'undefined') {110var c = {{{ EXPORT_NAME }}};111delete {{{ EXPORT_NAME }}};112ok(c);113} else {114ok();115}116#else117var c = {{{ EXPORT_NAME }}};118delete {{{ EXPORT_NAME }}};119ok(c);120#endif121#else122ok();123#endif124};125document.body.appendChild(s);126});127}128'''129130# Only one file to download - no need to use Promise.all()131if len(files_to_load) == 1:132if settings.MODULARIZE:133return script_xhr + files_to_load[0] + ".then(js);"134else:135return script_xhr + files_to_load[0] + ";"136137if not settings.MODULARIZE or settings.WASM_WORKERS:138# If downloading multiple files like .wasm or .mem, those need to be loaded in139# before we can add the main runtime script to the DOM, so convert the main .js140# script load from direct script() load to a binary() load so we can still141# immediately start the download, but can control when we add the script to the142# DOM.143if settings.PTHREADS or settings.WASM_WORKERS:144script_load = "script(url)"145else:146script_load = "script(url).then(() => URL.revokeObjectURL(url));"147148if settings.WASM_WORKERS:149save_js = f'{settings.EXPORT_NAME}.js = '150else:151save_js = ''152153files_to_load[0] = f"binary('{settings.TARGET_JS_NAME}')"154if not settings.MODULARIZE:155then_statements += ["var url = %sURL.createObjectURL(new Blob([r[0]], { type: 'application/javascript' }));" % save_js,156script_load]157158# Add in binary() XHR loader if used:159if any("binary(" in s for s in files_to_load + then_statements):160prefix_statements += [binary_xhr]161if any("script(" in s for s in files_to_load + then_statements):162prefix_statements += [script_xhr]163164# Several files to download, go via Promise.all()165load = '\n'.join(prefix_statements)166load += "Promise.all([" + ', '.join(files_to_load) + "])"167if len(then_statements) > 0:168load += '.then((r) => {\n %s\n});' % '\n '.join(then_statements)169return load170171172def generate_minimal_runtime_html(target, options, js_target, target_basename):173logger.debug('generating HTML for minimal runtime')174shell = utils.read_file(options.shell_path)175if settings.SINGLE_FILE:176# No extra files needed to download in a SINGLE_FILE build.177shell = shell.replace('{{{ DOWNLOAD_JS_AND_WASM_FILES }}}', '')178else:179shell = shell.replace('{{{ DOWNLOAD_JS_AND_WASM_FILES }}}', generate_minimal_runtime_load_statement(target_basename))180181temp_files = shared.get_temp_files()182with temp_files.get_file(suffix='.js') as shell_temp:183utils.write_file(shell_temp, shell)184shell = building.read_and_preprocess(shell_temp)185186if re.search(r'{{{\s*SCRIPT\s*}}}', shell):187shared.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.')188189shell = shell.replace('{{{ TARGET_BASENAME }}}', settings.TARGET_BASENAME)190shell = shell.replace('{{{ EXPORT_NAME }}}', settings.EXPORT_NAME)191shell = shell.replace('{{{ TARGET_JS_NAME }}}', settings.TARGET_JS_NAME)192193# In SINGLE_FILE build, embed the main .js file into the .html output194if settings.SINGLE_FILE:195js_contents = utils.read_file(js_target)196utils.delete_file(js_target)197else:198js_contents = ''199shell = shell.replace('{{{ JS_CONTENTS_IN_SINGLE_FILE_BUILD }}}', js_contents)200utils.write_file(target, shell, options.output_eol)201202203