#!/usr/bin/env python3

"""
Builds a Pyodide package.
"""

import argparse
import hashlib
import os
import shutil
import subprocess


import common


ROOTDIR = os.path.abspath(os.path.dirname(__file__))


def check_checksum(path, pkg):
    """
    Checks that a tarball matches the checksum in the package metadata.
    """
    if 'md5' not in pkg['source']:
        return
    checksum = pkg['source']['md5']
    CHUNK_SIZE = 1 << 16
    h = hashlib.md5()
    with open(path, 'rb') as fd:
        while True:
            chunk = fd.read(CHUNK_SIZE)
            h.update(chunk)
            if len(chunk) < CHUNK_SIZE:
                break
    if h.hexdigest() != checksum:
        raise ValueError("Invalid checksum")


def download_and_extract(buildpath, packagedir, pkg, args):
    tarballpath = os.path.join(
        buildpath, os.path.basename(pkg['source']['url']))
    if not os.path.isfile(tarballpath):
        subprocess.run([
            'wget', '-q', '-O', tarballpath, pkg['source']['url']
        ], check=True)
        check_checksum(tarballpath, pkg)
    srcpath = os.path.join(buildpath, packagedir)
    if not os.path.isdir(srcpath):
        shutil.unpack_archive(tarballpath, buildpath)
    return srcpath


def patch(path, srcpath, pkg, args):
    if os.path.isfile(os.path.join(srcpath, '.patched')):
        return

    # Apply all of the patches
    orig_dir = os.getcwd()
    pkgdir = os.path.abspath(os.path.dirname(path))
    os.chdir(srcpath)
    try:
        for patch in pkg['source'].get('patches', []):
            subprocess.run([
                'patch', '-p1', '--binary', '-i', os.path.join(pkgdir, patch)
            ], check=True)
    finally:
        os.chdir(orig_dir)

    # Add any extra files
    for src, dst in pkg['source'].get('extras', []):
        shutil.copyfile(os.path.join(pkgdir, src), os.path.join(srcpath, dst))

    with open(os.path.join(srcpath, '.patched'), 'wb') as fd:
        fd.write(b'\n')


def get_libdir(srcpath, args):
    # Get the name of the build/lib.XXX directory that distutils wrote its
    # output to
    slug = subprocess.check_output([
        os.path.join(args.host, 'bin', 'python3'),
        '-c',
        'import sysconfig, sys; '
        'print("{}-{}.{}".format('
        'sysconfig.get_platform(), '
        'sys.version_info[0], '
        'sys.version_info[1]))']).decode('ascii').strip()
    purelib = os.path.join(srcpath, 'build', 'lib')
    if os.path.isdir(purelib):
        libdir = purelib
    else:
        libdir = os.path.join(srcpath, 'build', 'lib.' + slug)
    return libdir


def compile(path, srcpath, pkg, args):
    if os.path.isfile(os.path.join(srcpath, '.built')):
        return

    orig_dir = os.getcwd()
    os.chdir(srcpath)
    try:
        subprocess.run([
            os.path.join(args.host, 'bin', 'python3'),
            os.path.join(ROOTDIR, 'pywasmcross'),
            '--cflags',
            args.cflags + ' ' +
            pkg.get('build', {}).get('cflags', ''),
            '--ldflags',
            args.ldflags + ' ' +
            pkg.get('build', {}).get('ldflags', ''),
            '--host', args.host,
            '--target', args.target], check=True)
    finally:
        os.chdir(orig_dir)

    post = pkg.get('build', {}).get('post')
    if post is not None:
        libdir = get_libdir(srcpath, args)
        pkgdir = os.path.abspath(os.path.dirname(path))
        env = {
            'BUILD': libdir,
            'PKGDIR': pkgdir
        }
        subprocess.run([
            'bash', '-c', post], env=env, check=True)

    with open(os.path.join(srcpath, '.built'), 'wb') as fd:
        fd.write(b'\n')


def package_files(buildpath, srcpath, pkg, args):
    if os.path.isfile(os.path.join(buildpath, '.packaged')):
        return

    name = pkg['package']['name']
    libdir = get_libdir(srcpath, args)
    subprocess.run([
        'python2',
        os.path.join(os.environ['EMSCRIPTEN'], 'tools', 'file_packager.py'),
        os.path.join(buildpath, name + '.data'),
        '--preload',
        '{}@/lib/python3.6/site-packages'.format(libdir),
        '--js-output={}'.format(os.path.join(buildpath, name + '.js')),
        '--export-name=pyodide',
        '--exclude', '*.wasm.pre',
        '--exclude', '__pycache__'], check=True)
    subprocess.run([
        'uglifyjs',
        os.path.join(buildpath, name + '.js'),
        '-o',
        os.path.join(buildpath, name + '.js')], check=True)

    with open(os.path.join(buildpath, '.packaged'), 'wb') as fd:
        fd.write(b'\n')


def build_package(path, args):
    pkg = common.parse_package(path)
    packagedir = pkg['package']['name'] + '-' + pkg['package']['version']
    dirpath = os.path.dirname(path)
    orig_path = os.getcwd()
    os.chdir(dirpath)
    try:
        buildpath = os.path.join(dirpath, 'build')
        if not os.path.exists(buildpath):
            os.makedirs(buildpath)
        srcpath = download_and_extract(buildpath, packagedir, pkg, args)
        patch(path, srcpath, pkg, args)
        compile(path, srcpath, pkg, args)
        package_files(buildpath, srcpath, pkg, args)
    finally:
        os.chdir(orig_path)


def parse_args():
    parser = argparse.ArgumentParser('Build a pyodide package.')
    parser.add_argument(
        'package', type=str, nargs=1,
        help="Path to meta.yaml package description")
    parser.add_argument(
        '--cflags', type=str, nargs='?', default=common.DEFAULTCFLAGS,
        help='Extra compiling flags')
    parser.add_argument(
        '--ldflags', type=str, nargs='?', default=common.DEFAULTLDFLAGS,
        help='Extra linking flags')
    parser.add_argument(
        '--host', type=str, nargs='?', default=common.HOSTPYTHON,
        help='The path to the host Python installation')
    parser.add_argument(
        '--target', type=str, nargs='?', default=common.TARGETPYTHON,
        help='The path to the target Python installation')
    return parser.parse_args()


def main(args):
    path = os.path.abspath(args.package[0])
    build_package(path, args)


if __name__ == '__main__':
    args = parse_args()
    main(args)

tools/buildpkg.py
#!/usr/bin/env python3

"""
Builds a Pyodide package.
"""

import argparse
import hashlib
import os
import shutil
import subprocess


import common


ROOTDIR = os.path.abspath(os.path.dirname(__file__))


def check_checksum(path, pkg):
    """
    Checks that a tarball matches the checksum in the package metadata.
    """
    if 'md5' not in pkg['source']:
        return
    checksum = pkg['source']['md5']
    CHUNK_SIZE = 1 << 16
    h = hashlib.md5()
    with open(path, 'rb') as fd:
        while True:
            chunk = fd.read(CHUNK_SIZE)
            h.update(chunk)
            if len(chunk) < CHUNK_SIZE:
                break
    if h.hexdigest() != checksum:
        raise ValueError("Invalid checksum")


def download_and_extract(buildpath, packagedir, pkg, args):
    tarballpath = os.path.join(
        buildpath, os.path.basename(pkg['source']['url']))
    if not os.path.isfile(tarballpath):
        subprocess.run([
            'wget', '-q', '-O', tarballpath, pkg['source']['url']
        ], check=True)
        check_checksum(tarballpath, pkg)
    srcpath = os.path.join(buildpath, packagedir)
    if not os.path.isdir(srcpath):
        shutil.unpack_archive(tarballpath, buildpath)
    return srcpath


def patch(path, srcpath, pkg, args):
    if os.path.isfile(os.path.join(srcpath, '.patched')):
        return

    # Apply all of the patches
    orig_dir = os.getcwd()
    pkgdir = os.path.abspath(os.path.dirname(path))
    os.chdir(srcpath)
    try:
        for patch in pkg['source'].get('patches', []):
            subprocess.run([
                'patch', '-p1', '--binary', '-i', os.path.join(pkgdir, patch)
            ], check=True)
    finally:
        os.chdir(orig_dir)

    # Add any extra files
    for src, dst in pkg['source'].get('extras', []):
        shutil.copyfile(os.path.join(pkgdir, src), os.path.join(srcpath, dst))

    with open(os.path.join(srcpath, '.patched'), 'wb') as fd:
        fd.write(b'\n')


def get_libdir(srcpath, args):
    # Get the name of the build/lib.XXX directory that distutils wrote its
    # output to
    slug = subprocess.check_output([
        os.path.join(args.host, 'bin', 'python3'),
        '-c',
        'import sysconfig, sys; '
        'print("{}-{}.{}".format('
        'sysconfig.get_platform(), '
        'sys.version_info[0], '
        'sys.version_info[1]))']).decode('ascii').strip()
    purelib = os.path.join(srcpath, 'build', 'lib')
    if os.path.isdir(purelib):
        libdir = purelib
    else:
        libdir = os.path.join(srcpath, 'build', 'lib.' + slug)
    return libdir


def compile(path, srcpath, pkg, args):
    if os.path.isfile(os.path.join(srcpath, '.built')):
        return

    orig_dir = os.getcwd()
    os.chdir(srcpath)
    try:
        subprocess.run([
            os.path.join(args.host, 'bin', 'python3'),
            os.path.join(ROOTDIR, 'pywasmcross'),
            '--cflags',
            args.cflags + ' ' +
            pkg.get('build', {}).get('cflags', ''),
            '--ldflags',
            args.ldflags + ' ' +
            pkg.get('build', {}).get('ldflags', ''),
            '--host', args.host,
            '--target', args.target], check=True)
    finally:
        os.chdir(orig_dir)

    post = pkg.get('build', {}).get('post')
    if post is not None:
        libdir = get_libdir(srcpath, args)
        pkgdir = os.path.abspath(os.path.dirname(path))
        env = {
            'BUILD': libdir,
            'PKGDIR': pkgdir
        }
        subprocess.run([
            'bash', '-c', post], env=env, check=True)

    with open(os.path.join(srcpath, '.built'), 'wb') as fd:
        fd.write(b'\n')


def package_files(buildpath, srcpath, pkg, args):
    if os.path.isfile(os.path.join(buildpath, '.packaged')):
        return

    name = pkg['package']['name']
    libdir = get_libdir(srcpath, args)
    subprocess.run([
        'python2',
        os.path.join(os.environ['EMSCRIPTEN'], 'tools', 'file_packager.py'),
        os.path.join(buildpath, name + '.data'),
        '--preload',
        '{}@/lib/python3.6/site-packages'.format(libdir),
        '--js-output={}'.format(os.path.join(buildpath, name + '.js')),
        '--export-name=pyodide',
        '--exclude', '*.wasm.pre',
        '--exclude', '__pycache__',
        '--use-preload-plugins'], check=True)
    subprocess.run([
        'uglifyjs',
        os.path.join(buildpath, name + '.js'),
        '-o',
        os.path.join(buildpath, name + '.js')], check=True)

    with open(os.path.join(buildpath, '.packaged'), 'wb') as fd:
        fd.write(b'\n')


def build_package(path, args):
    pkg = common.parse_package(path)
    packagedir = pkg['package']['name'] + '-' + pkg['package']['version']
    dirpath = os.path.dirname(path)
    orig_path = os.getcwd()
    os.chdir(dirpath)
    try:
        buildpath = os.path.join(dirpath, 'build')
        if not os.path.exists(buildpath):
            os.makedirs(buildpath)
        srcpath = download_and_extract(buildpath, packagedir, pkg, args)
        patch(path, srcpath, pkg, args)
        compile(path, srcpath, pkg, args)
        package_files(buildpath, srcpath, pkg, args)
    finally:
        os.chdir(orig_path)


def parse_args():
    parser = argparse.ArgumentParser('Build a pyodide package.')
    parser.add_argument(
        'package', type=str, nargs=1,
        help="Path to meta.yaml package description")
    parser.add_argument(
        '--cflags', type=str, nargs='?', default=common.DEFAULTCFLAGS,
        help='Extra compiling flags')
    parser.add_argument(
        '--ldflags', type=str, nargs='?', default=common.DEFAULTLDFLAGS,
        help='Extra linking flags')
    parser.add_argument(
        '--host', type=str, nargs='?', default=common.HOSTPYTHON,
        help='The path to the host Python installation')
    parser.add_argument(
        '--target', type=str, nargs='?', default=common.TARGETPYTHON,
        help='The path to the target Python installation')
    return parser.parse_args()


def main(args):
    path = os.path.abspath(args.package[0])
    build_package(path, args)


if __name__ == '__main__':
    args = parse_args()
    main(args)

tools/buildpkg.py
diff --git a/Makefile b/Makefile
index d879478edad..11f33f893f9 100644
--- a/Makefile
+++ b/Makefile
@@ -62,7 +62,7 @@ build/pyodide.asm.js: src/main.bc src/jsimport.bc src/jsproxy.bc src/js2python.b
 
 
 build/pyodide.asm.data: root/.built
-	python2 $(FILEPACKAGER) build/pyodide.asm.data --preload root/lib@lib --js-output=build/pyodide.asm.data.js
+	python2 $(FILEPACKAGER) build/pyodide.asm.data --preload root/lib@lib --js-output=build/pyodide.asm.data.js --use-preload-plugins
 	uglifyjs build/pyodide.asm.data.js -o build/pyodide.asm.data.js
 
 
diff --git a/emsdk/patches/async-module-loading.patch b/emsdk/patches/async-module-loading.patch
new file mode 100644
index 00000000000..7ed625be2b5
--- /dev/null
+++ b/emsdk/patches/async-module-loading.patch
@@ -0,0 +1,253 @@
+diff --git a/src/library.js b/src/library.js
+index 5fc87ab16..b8ead8fc0 100644
+--- a/src/library.js
++++ b/src/library.js
+@@ -1755,39 +1755,44 @@ LibraryManager.library = {
+       return handle;
+     }
+ 
++    var lib_module;
+     if (filename === '__self__') {
+       var handle = -1;
+-      var lib_module = Module;
++      lib_module = Module;
+     } else {
+-      var target = FS.findObject(filename);
+-      if (!target || target.isFolder || target.isDevice) {
+-        DLFCN.errorMsg = 'Could not find dynamic lib: ' + filename;
+-        return 0;
+-      }
+-      FS.forceLoadFile(target);
++      if (Module['preloadedWasm'] !== undefined &&
++          Module['preloadedWasm'][filename] !== undefined) {
++        lib_module = Module['preloadedWasm'][filename];
++      } else {
++        var target = FS.findObject(filename);
++        if (!target || target.isFolder || target.isDevice) {
++          DLFCN.errorMsg = 'Could not find dynamic lib: ' + filename;
++          return 0;
++        }
++        FS.forceLoadFile(target);
+ 
+-      var lib_module;
+-      try {
++        try {
+ #if WASM
+-        // the shared library is a shared wasm library (see tools/shared.py WebAssembly.make_shared_library)
+-        var lib_data = FS.readFile(filename, { encoding: 'binary' });
+-        if (!(lib_data instanceof Uint8Array)) lib_data = new Uint8Array(lib_data);
+-        //Module.printErr('libfile ' + filename + ' size: ' + lib_data.length);
+-        lib_module = loadWebAssemblyModule(lib_data);
++          // the shared library is a shared wasm library (see tools/shared.py WebAssembly.make_shared_library)
++          var lib_data = FS.readFile(filename, { encoding: 'binary' });
++          if (!(lib_data instanceof Uint8Array)) lib_data = new Uint8Array(lib_data);
++          //Module.printErr('libfile ' + filename + ' size: ' + lib_data.length);
++          lib_module = loadWebAssemblyModule(lib_data);
+ #else
+-        // the shared library is a JS file, which we eval
+-        var lib_data = FS.readFile(filename, { encoding: 'utf8' });
+-        lib_module = eval(lib_data)(
+-          alignFunctionTables(),
+-          Module
+-        );
++          // the shared library is a JS file, which we eval
++          var lib_data = FS.readFile(filename, { encoding: 'utf8' });
++          lib_module = eval(lib_data)(
++            alignFunctionTables(),
++            Module
++          );
+ #endif
+-      } catch (e) {
++        } catch (e) {
+ #if ASSERTIONS
+-        Module.printErr('Error in loading dynamic library: ' + e);
++          Module.printErr('Error in loading dynamic library: ' + e);
+ #endif
+-        DLFCN.errorMsg = 'Could not evaluate dynamic lib: ' + filename + '\n' + e;
+-        return 0;
++          DLFCN.errorMsg = 'Could not evaluate dynamic lib: ' + filename + '\n' + e;
++          return 0;
++        }
+       }
+ 
+       // Not all browsers support Object.keys().
+diff --git a/src/library_browser.js b/src/library_browser.js
+index 36738391e..4258835ea 100644
+--- a/src/library_browser.js
++++ b/src/library_browser.js
+@@ -225,6 +225,33 @@ var LibraryBrowser = {
+       };
+       Module['preloadPlugins'].push(audioPlugin);
+ 
++#if (WASM != 0) && (MAIN_MODULE != 0)
++      var wasmPlugin = {};
++      wasmPlugin['asyncWasmLoadPromise'] = new Promise(
++        function(resolve, reject) { return resolve(); });
++      wasmPlugin['canHandle'] = function(name) {
++        return !Module.noWasmDecoding && (name.endsWith('.so') || name.endsWith('.wasm'));
++      };
++      wasmPlugin['handle'] = function(byteArray, name, onload, onerror) {
++        // loadWebAssemblyModule can not load modules out-of-order, so rather
++        // than just running the promises in parallel, this makes a chain of
++        // promises to run in series.
++        this.asyncWasmLoadPromise = this.asyncWasmLoadPromise.then(
++          function() {
++            return Module.loadWebAssemblyModule(byteArray, true)
++          }).then(
++            function(module) {
++              Module.preloadedWasm[name] = module;
++              onload();
++            },
++            function(err) {
++              console.warn("Couldn't instantiate wasm: " + name + " '" + err + "'");
++              onerror();
++            });
++      };
++      Module['preloadPlugins'].push(wasmPlugin);
++#endif
++
+       // Canvas event setup
+ 
+       function pointerLockChange() {
+diff --git a/src/preamble.js b/src/preamble.js
+index a757e8300..f529fe148 100644
+--- a/src/preamble.js
++++ b/src/preamble.js
+@@ -1822,6 +1822,9 @@ function removeRunDependency(id) {
+ 
+ Module["preloadedImages"] = {}; // maps url to image data
+ Module["preloadedAudios"] = {}; // maps url to audio data
++#if (WASM != 0) && (MAIN_MODULE != 0)
++Module["preloadedWasm"] = {}; // maps url to wasm instance exports
++#endif
+ 
+ #if PGO
+ var PGOMonitor = {
+diff --git a/src/support.js b/src/support.js
+index f6c9842ff..99367db70 100644
+--- a/src/support.js
++++ b/src/support.js
+@@ -86,7 +86,7 @@ function loadDynamicLibrary(lib) {
+ 
+ #if WASM
+ // Loads a side module from binary data
+-function loadWebAssemblyModule(binary) {
++function loadWebAssemblyModule(binary, loadAsync) {
+   var int32View = new Uint32Array(new Uint8Array(binary.subarray(0, 24)).buffer);
+   assert(int32View[0] == 0x6d736100, 'need to see wasm magic number'); // \0wasm
+   // we should see the dylink section right after the magic number and wasm version
+@@ -166,59 +166,71 @@ function loadWebAssemblyModule(binary) {
+     oldTable.push(table.get(i));
+   }
+ #endif
+-  // create a module from the instance
+-  var instance = new WebAssembly.Instance(new WebAssembly.Module(binary), info);
++
++  function postInstantiation(instance) {
++    var exports = {};
+ #if ASSERTIONS
+-  // the table should be unchanged
+-  assert(table === originalTable);
+-  assert(table === Module['wasmTable']);
+-  if (instance.exports['table']) {
+-    assert(table === instance.exports['table']);
+-  }
+-  // the old part of the table should be unchanged
+-  for (var i = 0; i < oldTableSize; i++) {
+-    assert(table.get(i) === oldTable[i], 'old table entries must remain the same');
+-  }
+-  // verify that the new table region was filled in
+-  for (var i = 0; i < tableSize; i++) {
+-    assert(table.get(oldTableSize + i) !== undefined, 'table entry was not filled in');
+-  }
+-#endif
+-  var exports = {};
+-  for (var e in instance.exports) {
+-    var value = instance.exports[e];
+-    if (typeof value === 'object') {
+-      // a breaking change in the wasm spec, globals are now objects
+-      // https://github.com/WebAssembly/mutable-global/issues/1
+-      value = value.value;
++    // the table should be unchanged
++    assert(table === originalTable);
++    assert(table === Module['wasmTable']);
++    if (instance.exports['table']) {
++      assert(table === instance.exports['table']);
++    }
++    // the old part of the table should be unchanged
++    for (var i = 0; i < oldTableSize; i++) {
++      assert(table.get(i) === oldTable[i], 'old table entries must remain the same');
++    }
++    // verify that the new table region was filled in
++    for (var i = 0; i < tableSize; i++) {
++      assert(table.get(oldTableSize + i) !== undefined, 'table entry was not filled in');
+     }
+-    if (typeof value === 'number') {
+-      // relocate it - modules export the absolute value, they can't relocate before they export
++#endif
++    for (var e in instance.exports) {
++      var value = instance.exports[e];
++      if (typeof value === 'object') {
++        // a breaking change in the wasm spec, globals are now objects
++        // https://github.com/WebAssembly/mutable-global/issues/1
++        value = value.value;
++      }
++      if (typeof value === 'number') {
++        // relocate it - modules export the absolute value, they can't relocate before they export
+ #if EMULATED_FUNCTION_POINTERS
+-      // it may be a function pointer
+-      if (e.substr(0, 3) == 'fp$' && typeof instance.exports[e.substr(3)] === 'function') {
+-        value = value + env['tableBase'];
+-      } else {
++        // it may be a function pointer
++        if (e.substr(0, 3) == 'fp$' && typeof instance.exports[e.substr(3)] === 'function') {
++          value = value + env['tableBase'];
++        } else {
+ #endif
+-        value = value + env['memoryBase'];
++          value = value + env['memoryBase'];
+ #if EMULATED_FUNCTION_POINTERS
+-      }
++        }
+ #endif
++      }
++      exports[e] = value;
+     }
+-    exports[e] = value;
+-  }
+-  // initialize the module
+-  var init = exports['__post_instantiate'];
+-  if (init) {
+-    if (runtimeInitialized) {
+-      init();
+-    } else {
+-      // we aren't ready to run compiled code yet
+-      __ATINIT__.push(init);
++    // initialize the module
++    var init = exports['__post_instantiate'];
++    if (init) {
++      if (runtimeInitialized) {
++        init();
++      } else {
++        // we aren't ready to run compiled code yet
++        __ATINIT__.push(init);
++      }
+     }
++    return exports;
++  }
++
++  if (loadAsync) {
++    return WebAssembly.instantiate(binary, info).then(function(result) {
++      return postInstantiation(result.instance);
++    });
++  } else {
++    var instance = new WebAssembly.Instance(new WebAssembly.Module(binary), info);
++    return postInstantiation(instance);
+   }
+-  return exports;
+ }
++Module['loadWebAssemblyModule'] = loadWebAssemblyModule;
++
+ #endif // WASM
+ #endif // RELOCATABLE
+ 
diff --git a/src/pyodide.js b/src/pyodide.js
index 52fc51fe0bf..d104e461940 100644
--- a/src/pyodide.js
+++ b/src/pyodide.js
@@ -71,22 +71,44 @@ var languagePluginLoader = new Promise((resolve, reject) => {
   let Module = {};
   window.Module = Module;
 
+  Module.noImageDecoding = true;
+  Module.noAudioDecoding = true;
+  let isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
+  if (isFirefox) {
+    Module.noWasmDecoding = true;
+  }
+
   let wasm_promise = WebAssembly.compileStreaming(fetch(wasmURL));
   Module.instantiateWasm = (info, receiveInstance) => {
     wasm_promise.then(module => WebAssembly.instantiate(module, info))
         .then(instance => receiveInstance(instance));
     return {};
   };
+
   Module.filePackagePrefixURL = baseURL;
-  Module.postRun = () => {
-    delete window.Module;
-    fetch(`${baseURL}packages.json`)
+  var postRunPromise = new Promise((resolve, reject) => {
+    Module.postRun = () => {
+      delete window.Module;
+      fetch(`${baseURL}packages.json`)
         .then((response) => response.json())
         .then((json) => {
           window.pyodide.packages = json;
           resolve();
         });
-  };
+    };
+  });
+
+  var dataLoadPromise = new Promise((resolve, reject) => {
+    Module.monitorRunDependencies =
+        (n) => {
+          if (n === 0) {
+            delete Module.monitorRunDependencies;
+            resolve();
+          }
+        }
+  });
+
+  Promise.all([ postRunPromise, dataLoadPromise ]).then(() => resolve());
 
   let data_script = document.createElement('script');
   data_script.src = `${baseURL}pyodide.asm.data.js`;
diff --git a/tools/buildpkg.py b/tools/buildpkg.py
index b774086b450..0c5cb0d8dee 100755
--- a/tools/buildpkg.py
+++ b/tools/buildpkg.py
@@ -144,7 +144,8 @@ def package_files(buildpath, srcpath, pkg, args):
         '--js-output={}'.format(os.path.join(buildpath, name + '.js')),
         '--export-name=pyodide',
         '--exclude', '*.wasm.pre',
-        '--exclude', '__pycache__'], check=True)
+        '--exclude', '__pycache__',
+        '--use-preload-plugins'], check=True)
     subprocess.run([
         'uglifyjs',
         os.path.join(buildpath, name + '.js'),

Make work on Chrome

Make work on Chrome

