Moodle Playground WASM Status¶
Fecha de referencia: 2026-03-10
Objetivo¶
Levantar Moodle en navegador con php-wasm, evitando:
- descargar Moodle oficial en tiempo de ejecución;
- minimizar el tiempo de extracción de ~30k ficheros en MEMFS en cada arranque;
- depender de limpiar manualmente el Service Worker entre pruebas.
Arquitectura actual¶
Frontend¶
app.js- registra y re-registra el Service Worker en cada entrada;
- arranca el bootstrap;
- muestra logs de progreso y errores;
- recibe eventos de debug del Service Worker.
Service Worker¶
sw.js- se re-registra con cache-busting;
- intercepta
/moodle/*; - reenvía las requests al worker PHP por
BroadcastChannel; - devuelve la respuesta al navegador;
- expone debug de respuestas 4xx/5xx al panel.
PHP runtime¶
php-worker.js- ejecuta
PhpCgiWorkeren unDedicated Worker, no en el Service Worker; - hace bootstrap de Moodle;
- extrae el bundle ZIP de Moodle en MEMFS;
- genera
php.iniy helpers; - responde peticiones HTTP enviadas desde
sw.js; - serializa las requests para evitar reentrada simultánea sobre la misma instancia PHP.
Carga de Moodle¶
lib/moodle-loader.js- carga
assets/manifests/latest.json; - prioriza el bundle ZIP si existe;
- cachea bundle e índice en Cache Storage;
- ya no hace fallback a descarga remota de Moodle en runtime.
Extracción del bundle en MEMFS¶
lib/moodle-loader.jswriteEntriesToPhp()extrae el ZIP directamente en Emscripten MEMFS usando operaciones síncronas del FS (mkdirTree+writeFile) con deduplicación de directorios ancestro para rendimiento (~30k archivos en ~2s).- todos los ficheros quedan escribibles, lo que permite instalar plugins y aplicar parches en runtime sin restricciones.
Pipeline offline¶
Build¶
scripts/build-moodle-bundle.sh- descarga o reutiliza la release oficial;
- aplica parches offline al árbol fuente;
- genera el ZIP de Moodle;
-
genera el manifiesto final.
- genera
assets/manifests/latest.jsoncon hashes, tamaños y paths.
Dependencias runtime¶
scripts/sync-browser-deps.mjs- copia dependencias browser a
vendor/; - parchea rutas de
moduleRootpara que los.sose resuelvan bien; - parchea
PhpCgiBase.jspara completar variables CGI y corregirSCRIPT_NAME.
Parches a Moodle¶
scripts/patch-moodle-source.sh- añade
require_oncepararesponse_aware_exception.phpenlib/dmllib.php; - añade
require_onceparalib/classes/session/manager.phpeninstall.php; - añade
require_once(__DIR__.'/loader_interface.php')encache/classes/cache.php.
Qué se ha resuelto¶
Registro y ciclo del Service Worker¶
- Se eliminó el uso de
import()dentro del Service Worker. - El Service Worker se re-registra en cada carga.
- Ya no hace falta limpiar manualmente el SW entre intentos.
Runtime de PHP¶
- Se descartó ejecutar PHP dentro del Service Worker.
- PHP ahora vive en
Dedicated Worker, evitando incompatibilidades directas conServiceWorkerGlobalScope.
Descarga de Moodle¶
- Se eliminó la descarga remota desde
download.moodle.orgen runtime. - Ahora Moodle debe prepararse antes con:
make prepare-devmake bundle- o
make bundle-all
Bundle y tiempo de arranque¶
- El core de Moodle se extrae desde un ZIP preconstruido directamente en Emscripten MEMFS.
writeEntriesToPhp()usa operaciones síncronas del FS con deduplicación de directorios ancestro, lo que permite escribir ~30k archivos en ~2 segundos.
Extensiones PHP dinámicas¶
Se han integrado extensiones runtime mediante sharedLibs:
iconvintllibxmldomsimplexmlzlibzipmbstringopensslphar
CGI/env¶
Se corrigieron variables importantes:
SERVER_NAMESERVER_PORTSERVER_PROTOCOLSCRIPT_NAME
Esto arregló varios problemas de generación de URLs y warnings en el instalador.
Validación del bundle¶
Se comprueba la integridad del ZIP descargado mediante SHA-256 (verifyBundle() en
lib/moodle-loader.js). Si el checksum no coincide con el manifest, el bundle cacheado
se descarta y se re-descarga automáticamente.
Problemas encontrados durante la implementación¶
Autoload de Moodle¶
Han aparecido errores de clases/interfaces no encontradas que no deberían fallar en Moodle normal:
core\exception\response_aware_exceptioncore\session\managercore_cache\loader_interface
Se han ido parcheando offline con require_once explícitos.
Bridge SW <-> PHP worker¶
El bridge ha sufrido:
- timeouts de 15s, luego ampliados a 60s;
- peticiones paralelas al mismo runtime PHP;
- ausencia de visibilidad sobre qué request se estaba procesando.
Ahora:
- el timeout es mayor;
- las requests se serializan;
- se loguea inicio de cada request.
Estado actual¶
Lo que ya está validado¶
- bootstrap offline;
- bundle local y manifiesto local;
- bundle ZIP generado y extraído en MEMFS;
install.phpexiste y se lee con el tamaño correcto;- la request HTTP llega al worker PHP.
Bloqueo abierto principal¶
La request:
GET /moodle/install.php?lang=en
entra en el worker PHP, pero no completa. El síntoma actual es:
Handling PHP request GET ...- no aparece
Completed PHP request ... - el Service Worker termina devolviendo
PHP worker bridge timed out.
Eso significa que el bloqueo ya no está en:
- el fetch del bundle;
- el registro del Service Worker;
- la existencia del fichero
install.php; - la carga básica del bundle.
Ahora mismo el problema parece estar dentro de:
php.request()dephp-cgi-wasm;- o algún path interno de PHP/Moodle durante la primera ejecución del instalador.
Últimos cambios relevantes¶
- se añadió una sanity check tras cargar el bundle para verificar
install.php; - se aumentó el timeout del bridge a 60 segundos;
- se serializaron las requests HTTP al runtime PHP;
- se bajó
max_execution_timea 15s enlib/config-template.jspara intentar obtener un error PHP real antes que un timeout del bridge.
Comandos útiles¶
O por partes:
Siguiente paso recomendado¶
El siguiente trabajo útil no es seguir tocando el bundle de Moodle, sino instrumentar el runtime php-cgi-wasm para aislar dónde se queda bloqueado php.request() con una sola petición activa.
Orden recomendado:
- instrumentar
vendor/php-cgi-wasm/PhpCgiBase.jsalrededor demainyparseResponse; - comprobar si el cuelgue ocurre antes o después de
php.ccall('main', ...); - si el bloqueo está en el FS, inspeccionar la extracción del bundle en MEMFS;
- si el bloqueo es interno del runtime CGI, valorar cambiar de estrategia para servir la primera carga o usar otro modo de integración con
php-wasm.