Qt para WebAssembly
Qt para Webassembly permite ejecutar aplicaciones Qt en la web.
WebAssembly (abreviado Wasm) es un formato binario de instrucciones destinado a ser ejecutado en una máquina virtual, por ejemplo en un navegador web.
Con Qt para WebAssembly, puedes distribuir tu aplicación como una aplicación web que se ejecuta en un sandbox del navegador. Este enfoque es adecuado para aplicaciones web distribuidas que no requieren acceso completo a las capacidades del dispositivo host.
Nota: Qt for WebAssembly es una plataforma compatible, pero algunos módulos aún no lo son o se encuentran en Tech Preview. Ver Módulos Qt Soportados.
Primeros pasos con Qt para WebAssembly
Construir aplicaciones Qt para WebAssembly es similar a construir Qt para otras plataformas. Necesita instalar un SDK (Emscripten), instalar Qt (o construir Qt desde el código fuente), y finalmente, construir la aplicación. Existen algunas diferencias, por ejemplo, Qt para WebAssembly soporta menos módulos y menos características que otras compilaciones de Qt.
Instalación de Emscripten
Emscripten es una cadena de herramientas para compilar en WebAssembly. Te permite ejecutar Qt en la web a una velocidad casi nativa sin plugins de navegador.
Cada versión menor de Qt está orientada a una versión específica de Emscripten, que permanece inalterada en las versiones de parche. Los paquetes binarios de Qt se construyen utilizando la versión de Emscripten objetivo. Las aplicaciones deben utilizar la misma versión ya que Emscripten no garantiza la compatibilidad ABI entre versiones.
La versión soportada de Emscripten para 6.11.0 es Emscripten 4.0.7.
Consulte la documentación de Emscripten para obtener más información sobre la instalación del SDK de Emscripten.
Utilice emsdk para instalar versiones específicas de Emscripten. Por ejemplo, para instalarlo para 6.11.0 introduzca:
- ./emsdk install 4.0.7
- ./emsdk activar 4.0.7
Tras la instalación, deberías tener el compilador Emscripten en tu ruta. Compruébelo con el siguiente comando:
em++ --version
En Windows, Emscripten está en su ruta después de la instalación. En macOS o Linux necesitas añadirlo a tu ruta, así:
source /path/to/emsdk/emsdk_env.sh
Compruébalo con el siguiente comando:
em++ --version
Puedes compilar Qt desde el código fuente si necesitas más flexibilidad a la hora de seleccionar la versión de Emscripten. En este caso las versiones anteriores son versiones mínimas. Se espera que las versiones posteriores funcionen pero pueden introducir cambios de comportamiento que requieran hacer cambios en Qt.
Instalación de Qt
Descarga Qt desde la sección Descargas de tu cuenta Qt. Ofrecemos versiones para Linux, macOS y Windows como plataformas de desarrollo.
Las versiones binarias están diseñadas para ejecutarse en el mayor número posible de navegadores y existen versiones monohilo y multihilo. Las versiones binarias no son compatibles con funciones no estándar como Wasm SIMD y Wasm exceptions.
Creación de Qt a partir del código fuente
La compilación desde el código fuente permite establecer las opciones de configuración de Qt, como el soporte de hilos, el nivel ES de OpenGL o el soporte SIMD. Descarga los fuentes de Qt desde la sección Descargas de tu cuenta Qt.
Configura Qt como una compilación cruzada para la plataforma wasm-emscripten. Esto establece las opciones de configuración -static, -no-feature-thread, y -no-make examples. Puedes habilitar el soporte de hilos con la opción de configuración -feature-thread. Las compilaciones de bibliotecas compartidas no son compatibles.
Necesitas una compilación host de la misma versión de Qt. Además, establezca la ruta a la compilación host en la variable CMake QT_HOST_PATH o usando el argumento -qt-host-path configure.
./configure -qt-host-path /path/to/Qt -platform wasm-emscripten -prefix $PWD/qtbase
Nota: configure siempre utiliza el generador Ninja y la herramienta de construcción si un ejecutable ninja está disponible. Ninja es multiplataforma, rico en características, performante y recomendado en todas las plataformas. El uso de otros generadores puede funcionar pero no está soportado oficialmente.
En Windows, asegúrese de que tiene Mingw-w64 en su PATH y configure con lo siguiente:
configure -qt-host-path C:\Path\to\Qt -no-warnings-are-errors -platform wasm-emscripten -prefix %CD%\qtbase
Luego construya los módulos requeridos:
cmake --build . -t qtbase -t qtdeclarative [-t another_module]
Creación de aplicaciones en la línea de comandos
Qt para WebAssembly soporta la construcción de aplicaciones usando qmake y make, o CMake con ninja o make.
$ /path/to/qt-wasm/qtbase/bin/qt-cmake . $ cmake --build .
Nota: Cuando se utiliza CMake vainilla (a diferencia de qt-cmake en Linux o qt-cmake.bat en Windows) recuerde especificar un archivo toolchain con "-DCMAKE_TOOLCHAIN_FILE", como para cualquier otra construcción multiplataforma. Para más detalles ver aquí: Introducción a CMake.
La construcción de la aplicación genera varios archivos de salida, incluyendo un archivo .wasm que contiene la aplicación y el código Qt (enlazado estáticamente), un archivo .html que se puede abrir en el navegador para ejecutar la aplicación.
Nota: Emscripten produce archivos .wasm relativamente grandes en el nivel de depuración "-g". Considera enlazar con "-g2" para construcciones de depuración.
Ejecución de aplicaciones
Ejecutar la aplicación requiere un servidor web. Los archivos de salida de la compilación son todos de contenido estático, por lo que cualquier servidor web servirá. Algunos casos de uso pueden requerir una configuración especial del servidor, como proporcionar certificados https o establecer las cabeceras http necesarias para habilitar el soporte multihilo.
Emrun
Emscripten proporciona la utilidad emrun para ejecutar aplicaciones de prueba. Emrun inicia un servidor web, lanza un navegador, y también capturará y reenviará stdout/stderr (que normalmente irá a la consola JavaScript).
/path/to/emscripten/emrun --browser=firefox appname.html
Python http.servidor
Otra opción es iniciar un servidor web de desarrollo y luego lanzar el navegador web por separado. Una de las opciones más sencillas es http.server de Python:
python -m http.server
Tenga en cuenta que esto es sólo un servidor web simple y no soporta SharedArrayBuffer necesario para el threading, ya que no se envían las cabeceras COOP y COED necesarias que se mencionan a continuación.
qtwasmserver
Qt proporciona un servidor web para desarrolladores que utiliza mkcert para generar certificados https. Esto permite probar características web que requieren un contexto seguro. Tenga en cuenta que la entrega a través de http://localhost también se considera segura, sin necesidad de un certificado.
El servidor web también establece las cabeceras COOP y COEP a valores que permiten el soporte para SharedArrayBuffer y multi-threading.
El script qtwasmserver inicia un servidor que se enlaza a localhost por defecto. Puedes añadir direcciones adicionales usando el argumento de línea de comandos -a, o usar --all para enlazar con todas las direcciones disponibles.
El script qtwasmserver se encuentra en las fuentes en
qtbase/util/wasm/qtwasmserver/qtwasmserver.py
o puede ser instalado usando pip:
pip install qtwasmserver
Para más información sobre qtwasmserver qtwasmserver
Uso de qtwasmserver:
python /path/to/qtbase/util/wasm/qtwasmserver/qtwasmserver.py --all
Creación de aplicaciones con Qt Creator
Qt Creator:Construir aplicaciones para la Web.
Desplegar Aplicaciones en la Web
La construcción de una aplicación genera varios ficheros (sustituya "app" por el nombre de la aplicación en la tabla siguiente).
| Archivo generado | Breve descripción |
|---|---|
| app.html | Contenedor HTML |
| qtloader.js | API JavaScript para cargar aplicaciones Qt |
| app.js | Tiempo de ejecución de JavaScript generado por Emscripten |
| app.wasm | binario de la aplicación |
Puedes desplegar app.html tal cual, o descartarlo en favor de un archivo HTML personalizado. También es posible realizar pequeños ajustes, como cambiar la imagen de la pantalla de inicio del logotipo de Qt al logotipo de la aplicación. En ambos casos, qtloader.js proporciona una API JavaScript para cargar la aplicación.
Comprima el archivo Wasm utilizando gzip o brotli antes de desplegarlo, ya que ofrecen una mejor relación de compresión que las otras herramientas. Consulte Minimizar el tamaño de los binarios para obtener más información.
La activación de ciertas características, como el multihilo y SIMD, produce binarios .wasm que son incompatibles con navegadores que no soportan la característica activada. Es posible evitar esta limitación creando varios archivos .wasm y utilizando la detección de características de JavaScript para seleccionar el correcto, pero tenga en cuenta que Qt no proporciona ninguna funcionalidad para ello.
Uso de qtloader
Qt proporciona una API JavaScript para descargar, compilar e instanciar aplicaciones Qt para WebAssembly. Esta API de carga envuelve la funcionalidad de carga proporcionada por Emscripten, y proporciona características adicionales útiles para aplicaciones basadas en Qt. Se implementa en el archivo qtloader.js. Una copia de este archivo se escribe en el directorio de compilación en el momento de la compilación.
El uso típico es el siguiente:
const app_container_element = ...; const instance = await qtLoad({ qt: { containerElements: [ app_container_element ], onLoaded: () => { /* handle application load completed */ }, onExit: () => { /* handle application exit */ }, } });
El código llama a la función de carga qtLoad() con un objeto de configuración. Este objeto de configuración puede contener cualquier opción de configuración de emscripten, así como un objeto de configuración especial "qt". El objeto de configuración qt soporta las siguientes propiedades:
| Propiedad | Breve descripción |
|---|---|
| containerElements | Matriz de elementos contenedores HTML. La aplicación los ve como QScreens. |
| onLoaded | Callback para cuando la aplicación ha completado la carga. |
| onExit | Callback para cuando la aplicación sale. |
El array containerElements es la interfaz principal entre Qt y la página web, donde los elementos html de este array (típicamente elementos <div>) especifican la ubicación del contenido de la aplicación en la página web.
La aplicación ve cada elemento contenedor como una instancia de QScreen, y puede colocar ventanas de aplicación en las instancias de pantalla como es habitual. Las ventanas con el estado Qt::WindowFullScreen configurado utilizan toda el área de la pantalla, mientras que las ventanas que no son de "pantalla completa" obtienen decoraciones de ventana.
La función qtLoad() devuelve una promesa, que genera una instancia de Emscripten cuando se espera. La instancia proporciona acceso a las funciones exportadas por Embind. Qt exporta varias de estas funciones, y estas funciones conforman la instancia API.
Uso de la API de instancia de Qt
Qt proporciona varias funciones de instancia. Actualmente, éstas permiten añadir y eliminar elementos contenedores en tiempo de ejecución.
| Propiedad | Breve descripción |
|---|---|
| qtAddContainerElement | Añade un elemento contenedor. Al añadir un elemento se añadirá un nuevo QScreen. |
| qtRemoveContainerElement | Elimina un elemento contenedor y su correspondiente pantalla. |
| qtSetContainerElements | Establece todos los elementos contenedores |
| qtResizeContainerElement | Hace que Qt recoja los cambios en el tamaño del elemento contenedor. |
Adaptación al qtloader de Qt 6.6
Qt 6.6 incluye un nuevo qtloader con una implementación simplificada y un ámbito más reducido. Esto incluye cambios en la API que pueden requerir la portabilidad del código JavaScript de la aplicación. Qt proporciona una API de compatibilidad para facilitar la transición. Dependiendo del caso de uso hay varias formas de avanzar:
- Si utiliza directamente el archivo
app.htmlgenerado, este archivo también se actualizará en el momento de la compilación. No es necesaria ninguna acción. - Si utilizas el conjunto de características básicas de qtloader, puedes utilizar la API de compatibilidad incluida en Qt 6.6 como medida temporal. Esta API será eliminada en una futura versión; debes planear la actualización para utilizar el nuevo qtloader. Es necesario portar el paso 1 a continuación.
- Si utiliza funciones avanzadas (como la adición de elementos contenedores en tiempo de ejecución), deberá migrar al nuevo cargador o instancia API. Los pasos 1 y 2 son necesarios.
Pasos de migración
- Incluir el
app.js(JavaScript runtime generado por Emscripten) desde el archivo html de carga.<script src="app.js"></script>
Antes de Qt 6.6, qtloader cargaba y evaluaba este archivo JavaScript. Esto ya no se hace, y el archivo debe incluirse usando una etiqueta <script>.
- Puerto a utilizar el nuevo JavaScript y la API de instancia.
Consulte las secciones de documentación anteriores.
Navegadores compatibles
Escritorio
Qt para WebAssembly está desarrollado y probado en los siguientes navegadores:
- Chrome
- Firefox
- Safari
- Edge
Qt debería ejecutarse si el navegador soporta WebAssembly. Qt tiene un requisito fijo de WebGL, incluso si la propia aplicación no utiliza gráficos acelerados por hardware. Los navegadores que soportan WebAssembly a menudo soportan WebGL, aunque algunos navegadores ponen en su lista negra GPUs antiguas o no soportadas. s/qtloader.js proporciona APIs para comprobar si WebGL está disponible.
Qt no hace un uso directo de las características del sistema operativo y no hay diferencia si, por ejemplo, FireFox se ejecuta en Windows o macOS. Qt sí utiliza algunas adaptaciones del sistema operativo, por ejemplo para el manejo de las teclas ctrl/cmd en macOS.
Móvil
Las aplicaciones Qt para WebAssembly se ejecutan en navegadores móviles como Safari para móviles y Chrome para Android.
Módulos Qt compatibles
Qt para WebAssembly soporta un subconjunto de los módulos y características de Qt. Los módulos probados se enumeran a continuación, otros módulos pueden o no funcionar.
- Qt Core
- Qt GUI
- Qt Network
- Qt Widgets
- Qt Qml
- Qt Quick
- Qt Quick Controls
- Qt Quick Diseños
- Qt 5 Core Compatibility APIs
- Qt Image Formats
- Qt OpenGL
- Qt SVG
- Qt WebSockets
- Qt Concurrent
- Qt Charts
- Qt Graphs
- Qt Quick 3D
Los siguientes módulos se encuentran en fase de Previsualización Tecnológica. Es posible que tengan una funcionalidad limitada o que cambien significativamente en futuras versiones.
En todos los casos, el soporte del módulo puede no ser completo y puede haber limitaciones adicionales, ya sea debido a la caja de arena del navegador o debido a lo incompleto del puerto de la plataforma Qt. Para más información, consulte Desarrollo con Qt para WebAssembly.
Desarrollo con Qt para WebAssembly
Construyendo con CMake
Si se necesita una configuración específica de Emscripten en CMake, se puede utilizar el siguiente código:
if(EMSCRIPTEN)
# WebAssembly specific code
else()
# other platforms
endif()Este código permite acomodar configuraciones específicas de Emscripten a la vez que asegura la compatibilidad con otras plataformas.
OpenGL y WebGL
Qt para WebAssembly soporta renderizado acelerado por hardware utilizando https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API WebGL.
WebGL se ajusta estrechamente a OpenGL ES, con el siguiente mapeo de versiones:
| OpenGL | WebGL |
|---|---|
| OpenGL ES 2.0 | WebGL 1 |
| OpengL ES 3.0 | WebGL 2 |
Qt utiliza la versión más alta disponible de WebGL. Normalmente es WebGL 2 en los navegadores actuales, pero puede ser WebGL 1 si está limitado por el hardware. Recomendamos utilizar Qt para WebAssembly en dispositivos que soporten WebGL 2.
Las diferencias entre WebGL y OpenGL de escritorio están documentadas en Diferencias entre WebGL y OpenGL. Existen diferencias adicionales entre WebGL 1.0 y WebGL 2.0, documentadas en la Especificación WebGL 2.0.
Por defecto se utiliza un subconjunto de ES2 (y ES3) compatible con WebGL. Si necesita utilizar glDrawArrays y glDrawElements sin búferes delimitados, puede activar la compatibilidad completa con ES2 añadiendo
target_link_options(<your target> PRIVATE -s FULL_ES2=1)
y/o la emulación completa de ES3 añadiendo
target_link_options(<your target> PRIVATE -s FULL_ES3=1)
a su proyecto CMakeLists.txt.
Para más información sobre la compatibilidad de Emscripten con OpenGL, visite https://emscripten.org/docs/porting/multimedia_and_graphics/OpenGL-support.html.
Limitaciones del contexto OpenGL
WebGL no soporta múltiples contextos por superficie. Esto tiene implicaciones para las aplicaciones que utilizan QOpenGLContext directa o indirectamente a través de otras clases como QOpenGLWidget.
Cada instancia de QOpenGLContext debe utilizarse con una única superficie. En la práctica, el contexto se asocia a la superficie en la primera llamada a makeCurrent(). No se admite la llamada a makeCurrent() en una superficie diferente después, o desde un QOpenGLContext diferente con la misma superficie.
No es posible compartir el contexto OpenGL. Llamar a QOpenGLContext::setShareContext() no tiene ningún efecto, y QOpenGLContext::shareContext() siempre devuelve nullptr.
La destrucción de una superficie (por ejemplo, QWindow) provoca la pérdida del contexto asociado. La aplicación debe manejar esto recreando el contexto.
QOpenGLWidget y otras clases que utilicen internamente la compartición de contexto no están soportadas.
Multihilo
Qt para WebAssembly soporta multihilo usando el soporte Pthreads de Emscripten, donde cada hilo está respaldado por un web worker. Habilite el multithreading instalando el componente "WebAssembly (multi-threaded)" desde Qt Maintenance Tool, o compilando Qt desde el código fuente y pasando la bandera "-feature-thread" para configurarlo.
Por lo general, se puede reutilizar el código de subprocesos existente, pero puede ser necesario modificarlo para que funcione con las características específicas de la implementación de pthread. Algunas características de Emscripten y Qt no están soportadas, esto incluye la característica de thread proxying y el bucle de renderizado threaded Qt Quick.
Ten en cuenta que es especialmente importante no bloquear el hilo principal en Qt para WebAssembly, ya que el hilo principal puede ser requerido para atender peticiones de hilos secundarios. Por ejemplo, todos los temporizadores en Qt están programados en el hilo principal, y no se activarán si el hilo principal está bloqueado. Otro ejemplo es que la creación de un nuevo web worker (para un hilo) sólo puede hacerse desde el hilo principal.
Emscripten proporciona algunas mitigaciones para esto. Las esperas cortas, como la adquisición de un bloqueo mutex, son soportadas por la espera ocupada y el procesamiento de eventos mientras se espera el bloqueo. Las esperas más largas en el hilo principal deben evitarse. En particular, la práctica común de llamar a QThread::wait() o pthread_join() para esperar a un hilo secundario no funcionará, a menos que la aplicación pueda garantizar que el hilo (y el web worker) ya ha sido iniciado, y será capaz de completarse sin ayuda del hilo principal en el momento en que se realice la llamada a wait() o join().
La función multithreading requiere que el navegador soporte la API SharedArrayBuffer. (Normalmente, Emscripten almacena el montón en un objeto ArrayBuffer. Para el multithreading, el heap debe ser compartido con los web workers y se necesita un SharedArrayBuffer) Esta API está generalmente disponible en todos los navegadores modernos, pero puede estar deshabilitada si no se cumplen ciertos requisitos de seguridad. Los binarios WebAssembly con soporte de threads habilitado fallarán entonces al ejecutarse, también si el binario no inicia realmente un thread.
Habilitar SharedArrayBuffer requiere un contexto de navegación seguro (donde la página se sirve a través de https:// o http://localhost), y que la página esté en modo aislado de origen cruzado. Esto último se puede conseguir configurando las cabeceras COOP y COEP en el servidor web:
- Cross-Origin-Opener-Policy: same-origin
- Cross-Origin-Embedder-Policy: require-corp
SIMD
Emscripten soporta WebAssembly SIMD, que proporciona tipos y operaciones SIMD de 128 bits para WebAssembly.
Compila Qt desde el código fuente y configúralo con la opción -feature-wasm-simd128 para habilitarlo; esto pasará la opción -msimd128 en tiempo de compilación y enlace. Tenga en cuenta que Qt no contiene rutas de código optimizado wasm-simd en este punto, sin embargo, habilitar wasm-simd habilitará la auto-vectorización del compilador donde el compilador puede utilizar las instrucciones SIMD.
Puede apuntar directamente a WebAssembly SIMD utilizando GCC/Clang SIMD Vector Extensions o WASM SIMD128 intrinsics. Para obtener más información, consulte la documentación de Emscripten SIMD .
Además, Emscripten soporta la emulación/traducción de instrucciones x86 SSE a instrucciones Wasm SIMD. Qt no utiliza esta emulación, ya que el uso de instrucciones SSE SIMD que no tienen un equivalente Wasm SIMD nativo puede causar un rendimiento reducido.
Tenga en cuenta que los binarios habilitados para SIMD son incompatibles con los navegadores que no soportan WebAssembly SIMD, también si las rutas de código SIMD no se llaman en tiempo de ejecución. Puede ser necesario habilitar el soporte SIMD en las configuraciones avanzadas de los navegadores, como 'about:config' o 'chrome:flags'.
Redes
Qt proporciona un soporte limitado para redes. En general, los protocolos de red que ya están en uso en la web pueden ser utilizados también desde Qt, mientras que otros no están directamente disponibles debido a la web sandbox.
Los siguientes protocolos están soportados:
- QNetworkAccessManager Peticiones http al servidor de origen de la página web, o a un servidor que soporte CORS. Esto incluye XMLHttpRequest de QML.
- QWebSocket conexiones a cualquier host. Tenga en cuenta que las páginas web servidas a través del protocolo seguro https sólo permiten conexiones websockets a través del protocolo seguro wss.
- Sockets POSIX TCP emulados sobre WebSockets, utilizando la funcionalidad proporcionada por Emscripten. Tenga en cuenta que esto requiere ejecutar un servidor de reenvío que gestione la traducción de sockets.
El resto de protocolos de red no están soportados.
Nota: QWebSocketServer no es compatible debido a las limitaciones del navegador. Los navegadores restringen la funcionalidad de socket del lado del servidor para garantizar la seguridad dentro de la caja de arena web. En consecuencia, cualquier funcionalidad que dependa de QWebSocketServer para aceptar conexiones de red entrantes no puede utilizarse dentro del entorno web.
Nota: Los módulosQtMqtt y QtRemoteObjects pueden utilizarse con QtWebSockets como transportes. No están soportados oficialmente y pueden o no funcionar, o tener funcionalidad faltante. Ver QMqttClient::connectToHostWebSocket y el ejemplo QtRemoteObjects WebSockets Applications.
Intercambio de recursos entre orígenes (CORS) y política (CORP)
Las aplicaciones WebAssembly que utilizan redes pueden requerir que el servidor establezca encabezados de respuesta CORS (Cross-Origin Resource Sharing) y CORP (Cross-Origin Resource Policy). Estos restringen las peticiones HTTP que se originan en diferentes dominios, lo que supone un riesgo para la seguridad.
Usando QHttpServer, el siguiente es un ejemplo para establecer estas cabeceras:
auto headers = response.headers(); headers.append("Access-Control-Allow-Origin", "*"); headers.append("Access-Control-Allow-Origin", "localhost"); headers.append("Access-Control-Allow-Methods", "POST", "GET","OPTIONS"); headers.append("Cross-Origin-Opener-Policy", "same-origin"); headers.append("Cross-Origin-Embedder-Policy", "require-corp"); headers.append("Cross-Origin-Resource-Policy", "cross-origin");
Otros servidores conocidos tienen su propia forma de configurar el envío de estas cabeceras.
El uso de un comodín "*" para Access-Control-Allow-Origin, dará lugar a un error, si se utiliza con Access-Control-Allow-Credentials.
El script de utilidad incluido qtwasmserver.py establecerá estas cabeceras si se utiliza la opción -cross-origin-isolation.
Acceso a archivos locales
El acceso al sistema de archivos está aislado en la Web, y esto tiene implicaciones en la forma en que la aplicación trabaja con los archivos. La plataforma Web proporciona APIs para acceder al sistema de archivos local de forma que esté bajo el control del usuario, así como APIs para acceder al almacenamiento persistente. Emscripten y Qt envuelven estas características y proporcionan APIs que son más fáciles de usar desde C++ y aplicaciones basadas en Qt.
La plataforma web proporciona funciones para acceder a archivos locales y almacenamiento persistente:
- <input type="file"> para mostrar un diálogo nativo de abrir archivo donde el usuario puede elegir un archivo.
- IndexedDB proporciona almacenamiento local persistente (no accesible fuera del navegador).
Emscripten proporciona varios sistemas de archivos con una API similar a POSIX. Estos incluyen:
- el sistema de archivos efímero MEMFS que almacena archivos en memoria
- el sistema de ficheros persistente IDBFS que almacena ficheros usando IndexedDB
Emscripten monta un sistema de archivos temporal MEMFS en "/" al inicio de la aplicación. Esto significa que QFile puede ser usado, y leerá y escribirá archivos en memoria por defecto. No se conserva al recargar el navegador.
Dado que las páginas web no tienen acceso directo al sistema de archivos local, Qt proporciona una API QFileDialog para su uso con Qt para WebAssembly.
- QFileDialog::getOpenFileContent() abre un diálogo de archivo nativo donde el usuario puede elegir un archivo
- QFileDialog::saveFileContent() guarda un archivo en el sistema de archivos local mediante descarga de archivos
Acceso al portapapeles
Qt soporta copiar y pegar texto, urls, tipos de archivo conocidos e imágenes con el portapapeles del sistema, con algunas diferencias debidas al sandbox web. No soporta datos binarios arbitrarios de tipo application/octet-stream. En general, el acceso al portapapeles requiere el permiso del usuario, que puede obtenerse gestionando un evento de entrada (por ejemplo, CTRL+c), o utilizando la API del Portapapeles.
Es preferible utilizar navegadores compatibles con la API del Portapapeles. Tenga en cuenta que un requisito para esta API es que la página web se sirva a través de una conexión segura (por ejemplo, https), y que algunos navegadores pueden requerir el cambio de las opciones de configuración.
- Chrome versión 66 y Safari versión 13.1 son compatibles con la API del Portapapeles.
- Firefox versión 90 soporta la API del Portapapeles si habilitas las siguientes opciones en 'about:config':
dom.events.asyncClipboard.read dom.events.asyncClipboard.clipboardItem
Fuentes
El módulo Qt WASM contiene 3 fuentes incrustadas: "Bitstream Vera Sans" (fuente de reserva), "DejaVu Sans", "DejaVu Sans Mono".
Estas fuentes proporcionan un juego de caracteres limitado. Qt ofrece varias opciones para añadir fuentes adicionales:
Una es usar FontLoader en QML, que puede obtener una fuente por URL o usando el sistema de recursos de Qt (de la misma forma que funcionan las aplicaciones de escritorio habituales).
La otra forma de usar fuentes es añadirlas a través de QFontDatabase::addApplicationFontFromData.
Accesibilidad y lectores de pantalla
Qt para WebAssembly proporciona soporte básico para lectores de pantalla. Los elementos de interfaz de usuario sencillos, como botones y casillas de verificación, funcionan, mientras que los elementos de interfaz de usuario más complejos, como las vistas de tabla o de árbol, pueden carecer de soporte. Tanto Qt Widgets como Qt Quick son compatibles.
Las siguientes configuraciones de navegadores y lectores de pantalla se han probado y se sabe que funcionan. Otros navegadores y lectores de pantalla pueden funcionar.
- VoiceOver con Safari en macOS
- VoiceOver con Chrome en macOS
La implementación de la accesibilidad funciona mediante la creación de elementos html "sombra" que proporcionan la información de accesibilidad para los elementos Qt UI. Esta funcionalidad está desactivada por defecto. El usuario final puede activarla seleccionando el botón "activar lector de pantalla" mediante el lector de pantalla. Cuando se activa, la página web se rellena con elementos de accesibilidad.
Inicio de la aplicación y bucle de eventos
Qt para WebAssembly soporta el método estándar de inicio de Qt, donde la aplicación crea un objeto QApplication y llama a la función exec:
int main(int argc, char **argv) { QApplication app(argc, argv); QWindow appWindow; return app.exec(); }
La llamada a exec() normalmente bloquea y procesa los eventos hasta el cierre de la aplicación. Desafortunadamente, esto no es posible en la plataforma web, donde no se permite bloquear el hilo principal. En su lugar, el control debe ser devuelto al bucle de eventos del navegador después de procesar cada evento.
Qt soluciona esto haciendo que exec() devuelva el control del hilo principal al navegador, preservando la pila. Desde el punto de vista del código de la aplicación, se entra en la función exec() y el procesamiento de eventos ocurre como de costumbre. Sin embargo, la llamada a exec() nunca retorna, tampoco al salir de la aplicación.
Este comportamiento es normalmente aceptable ya que el navegador liberará memoria de la aplicación en el momento del cierre de la aplicación. Esto significa que el código de cierre no se ejecuta, ya que el objeto de la aplicación se filtra y su destructor no se ejecuta.
Puedes evitar esto reescribiendo main() para que sea asíncrono, lo que es posible ya que Emscripten no sale del tiempo de ejecución cuando main() retorna. El código de la aplicación entonces omite hacer la llamada a exec(), y puede cerrar Qt limpiamente borrando la ventana de nivel superior y los objetos de la aplicación.
QApplication *g_app = nullptr; AppWindow *g_appWindow = nullptr; int main(int argc, char **argv) { g_app = new QApplication(argc, argv); g_appWindow = new AppWindow(); return 0; }
Asyncify y JSPI
La compilación por defecto de Qt para WebAssembly no permite volver a entrar en el bucle de eventos, como por ejemplo llamando a QEventLoop::exec() o QDialog::exec(), debido a las restricciones de la plataforma web que impiden que el código C++ síncrono invoque APIs JavaScript asíncronas.
Entre las funciones que requieren asyncify/JSPI se incluyen:
- QDialogs, QMessageBoxes con valores de retorno.
- Arrastrar y soltar (específicamente arrastrar).
- Bucles de eventos anidados/secundarios exec().
Emscripten proporciona soporte para trabajar alrededor de estas limitaciones usando la característica Asyncify. Esta función está disponible en dos versiones:
- Asyncify, implementada mediante un paso de post-procesamiento de código WebAssembly.
- JSPI, implementada mediante la función WebAssembly JS Promise Integration.
Consulte las secciones siguientes para obtener más información sobre cómo activar cada opción y las ventajas y desventajas que conlleva. En resumen, Asyncify está disponible hoy en día pero introduce una sobrecarga en forma de tiempos de compilación más largos, tamaños binarios más grandes y costes de rendimiento en tiempo de ejecución. JSPI tiene una sobrecarga mínima o nula, pero aún no es compatible con todos los navegadores.
Asyncify
Habilite asyncify añadiendo la opción "-sASYNCIFY -Os" a las opciones del enlazador:
CMake:
target_link_options(<your target> PUBLIC -sASYNCIFY -Os)
qmake:
QMAKE_LFLAGS += -sASYNCIFY -Os
Habilitar asyncify añade sobrecarga en forma de aumento del tamaño de los binarios y aumento del uso de la CPU. Construye con las optimizaciones activadas para minimizar la sobrecarga.
JSPI (Integración JS Promise)
A diferencia de Asyncify, usar JSPI requiere compilar Qt desde el código fuente. Pasa las banderas -feature-wasm-jspi -feature-wasm-exceptions al script de configuración de Qt para habilitarlo. (JSPI no es compatible con las excepciones emuladas por defecto de Emscripten).
Después, habilita JSPI para tu aplicación añadiendo la opción -sJSPI a las opciones del enlazador:
CMake:
target_link_options(<your target> PUBLIC -sJSPI)
qmake:
QMAKE_LFLAGS += -sJSPI
Depuración y perfiles
La depuración de Wasm se realiza en la consola JavaScript del navegador. La depuración de aplicaciones en Wasm directamente dentro de Qt Creator no es posible.
- La salida de depuración y registro de Qt se imprime en la consola JavaScript, a la que se puede acceder a través de las "Herramientas para desarrolladores" del navegador o similar.
- Los mapas de fuentes para recorrer el código, pueden ser creados reconfigurando Qt con la opción -device-option QT_WASM_SOURCE_MAP=1, y construyendo una compilación de depuración.
- Los símbolos de depuración a través de DWARF también están habilitados si el programa está vinculado con la bandera -g (probado con Chrome)
- Esto requerirá esta extensión: https://goo.gle/wasm-debugging-extension
- Véase también https://developer.chrome.com/blog/wasm-debugging-2020/
- Los navegadores móviles pueden utilizar la depuración remota
- Para detener la ejecución en una determinada línea y abrir el depurador del navegador mediante programación, puede añadir la función emscripten_debugger(); al código fuente de la aplicación.
- La creación de perfiles se puede realizar utilizando una compilación de depuración y las funciones de creación de perfiles de la consola de JavaScript. Qt añade -profiling-funcs a los argumentos del enlazador en las compilaciones de depuración, que conservan los nombres de las funciones en la creación de perfiles.
Puedes añadir más verbosidad para ayudar a depurar usando los argumentos del enlazador Emscripten:
- -s LIBRARY_DEBUG=1 (imprime las llamadas a bibliotecas)
- -s SYSCALL_DEBUG=1 (imprime las llamadas al sistema)
- -s FS_LOG=1 (imprime las operaciones del sistema de ficheros)
- -s SOCKET_DEBUG (imprime el socket, transferencia de datos de red)
CMake:
target_link_options(<your target> PRIVATE -s LIBRARY_DEBUG=1)
qmake:
QMAKE_LFLAGS_DEBUG += -s LIBRARY_DEBUG=1
Optimización de
Qt para WebAssembly utiliza la cadena de herramientas Emscripten para generar binarios, y hay muchos indicadores que pueden afectar al rendimiento y al tamaño de los binarios. Consulte Emscripten: Optimización del código para obtener más información.
Puedes pasar banderas del enlazador y del compilador igual que para las aplicaciones C++ normales:
target_compile_options(<your target> PRIVATE -oz -flto) target_link_options(<your target> PRIVATE -flto)
QMAKE_CXXFLAGS += -oz -flto QMAKE_LFLAGS += -flto
Minimizar el tamaño de los binarios
Para ofrecer una experiencia de usuario sin interrupciones, es importante reducir el tiempo de descarga y carga de las aplicaciones WebAssembly. Un binario de aplicación más pequeño es uno de los aspectos importantes que permiten una descarga más rápida. Utilice las siguientes alternativas para reducir el tamaño de los binarios:
- Asegúrese de distribuir compilaciones de versión. Las versiones de depuración contienen símbolos de depuración y son mucho más grandes.
- Habilite la compresión en un servidor. Los algoritmos más comunes como
gzipy Brotli funcionan bien en binarios Wasm y pueden reducir drásticamente su tamaño. - Pruebe las opciones del compilador y del enlazador que pueden resultar en la generación de binarios más pequeños (es decir, '-os', '-oz'). Los resultados variarán en función de la aplicación concreta.
- Desactive las funciones no deseadas al compilar Qt para WebAssembly desde el código fuente (véase más adelante).
Exclusión de funciones
Por defecto, una aplicación WebAssembly enlaza estáticamente con las bibliotecas Qt, lo que permite al compilador eliminar el código muerto. Sin embargo, debido a la naturaleza dinámica de Qt, no siempre es posible que el compilador realice tales optimizaciones.
Si compilas Qt para WebAssembly desde el código fuente, puedes deshabilitar características para reducir el tamaño de los binarios Qt, y -como resultado- el tamaño de los binarios .wasm. Qt desactiva algunas funciones de forma predeterminada para la plataforma WebAssembly, pero usted también puede desactivar funciones que su aplicación no utilice. Ver características des habilitadas para más información.
Puede desactivar las siguientes funciones para reducir el tamaño de los binarios (normalmente en un 10-15%):
| Configurar Argumento | Breve descripción |
|---|---|
| -no-feature-cssparser | Parser para hojas de estilo en cascada. |
| -no-feature-datetimeedit | Edición de fechas y horas (depende de datetimeparser). |
| -no-feature-datetimeparser | Análisis de fechas y horas. |
| -no-feature-dockwidget | Acoplar widgets dentro de QMainWindow o hacerlos flotar como ventanas de nivel superior en el escritorio. |
| -no-feature-gestures | Framework para gestos. |
| -no-feature-mimetype | Gestión de mimetypes. |
| -no-feature-qml-network | Transparencia de red. |
| -no-feature-qml-list-model | ListModel Tipo QML. |
| -no-feature-qml-table-model | TableModel Tipo QML. |
| -no-feature-quick-canvas | Elemento de lienzo. |
| -no-feature-quick-path | Elementos de ruta. |
| -no-feature-quick-pathview | PathView elemento. |
| -no-feature-quick-treeview | TreeView elemento. |
| -no-feature-style-stylesheet | Estilo del widget configurable mediante CSS. |
| -no-feature-tableview | Implementación por defecto del modelo/vista de una vista de tabla. |
| -no-feature-texthtmlparser | Parser para HTML. |
| -no-feature-textmarkdownreader | Lector de Markdown (CommonMark y GitHub). |
| -no-feature-textodfwriter | Escritor ODF. |
Excepciones Wasm
Qt se construye sin soporte de excepciones por defecto, donde lanzar una excepción abortará el programa. Las excepciones de WebAssembly pueden activarse compilando desde el código fuente y pasando la opción -feature-wasm-exceptions a Qt configure. Esto pasará la bandera -fwasm-exceptions al compilador en tiempo de compilación y enlace. Qt no permite habilitar el soporte de Emscripten para la anterior implementación de excepciones basada en JavaScript.
Tenga en cuenta que llamar a QApplication::exec() no está soportado cuando las excepciones están habilitadas, debido a detalles internos de implementación. En su lugar, escriba main() de forma que retorne pronto y no llame a exec(), como se describe en Inicio de la aplicación y el bucle de eventos.
Bibliotecas compartidas y enlazado dinámico (Technology Preview)
Qt para WebAssembly utiliza la vinculación estática por defecto, donde la aplicación se despliega como un único archivo WebAssembly que contiene las bibliotecas Qt y el código de la aplicación. La vinculación dinámica es un modo de compilación alternativo en el que cada biblioteca y complemento se distribuye individualmente.
Por ejemplo, una aplicación que utiliza Qt Quick puede hacer uso de las siguientes bibliotecas y plugins:
- <qtpath>/lib/libQt6Core.so
- <qtpath>/lib/libQt6Gui.so
- <qtpath>/lib/libQt6Qml.so
- <qtpath>/lib/libQt6Quick.so
- <qtpath>/plugins/imageformats/libqjpeg.so
- <qtpath>/plugins/imageformats/libqjgif.so
- <qtpath>/qml/QtQuick/Window/libquickwindowplugin.so
El soporte de enlaces dinámicos en 6.11 está en Technology Preview. La implementación es adecuada para la creación de prototipos y la evaluación, pero no para el uso en producción. Las limitaciones y restricciones actuales incluyen:
- No soporta multithreading.
- Asyncify no está soportado.
Las aplicaciones Qt creadas con enlazado dinámico requieren la presencia de dos archivos adicionales junto a los binarios: qt_plugins.json y qt_qml_imports.json. Estos archivos especifican una lista de bibliotecas compartidas que se cargarán al iniciar la aplicación. Hay una herramienta de ayuda disponible para generarlos: wasmdeployqt. Para demostrar cómo utilizar esta herramienta, puede ejecutarla con el indicador -help para obtener una visión general de los indicadores necesarios para ejecutar la herramienta y el uso de ejemplo.
El servidor web que aloja la aplicación debe tener disponibles las bibliotecas compartidas Qt. Esto se puede conseguir copiando el contenido de la carpeta de instalación de Qt en el servidor web, o creando un enlace en el sistema de archivos.
Inicio rápido
El procedimiento de compilación y despliegue es ligeramente diferente al de las compilaciones estáticas de wasm y escritorio compartido. Considere la posibilidad de comenzar con un pequeño ejemplo antes de pasar a la compilación de una aplicación completa.
- Construya Qt desde el código fuente, pase la opción "-shared" al script de configuración de Qt. Utilice la opción "-prefix" para establecer el directorio de instalación.
- Construye tu aplicación usando Qt desde el paso 1.
- Crea listas de precarga de plugins ejecutando la herramienta de despliegue desde el directorio de construcción de tu aplicación.
- <qtpath>/qtbase/bin/wasmdeployqt -qt-wasm-dir=<Ruta al directorio de Qt para WebAssembly> -qml-root-path=<Directorio raíz para los archivos QML> -qt-host-dir=<Ruta al directorio host de Qt>
Despliegue de bibliotecas compartidas en profundidad
La compilación de las bibliotecas compartidas de Qt se despliega en dos etapas, la primera de las cuales hace que la compilación de Qt y la aplicación estén disponibles para su descarga desde el servidor web, y la segunda etapa descarga los plugins de Qt necesarios y las importaciones de Qt Quick al iniciar la aplicación.
En el primer paso, haz que la instalación de Qt esté disponible para su descarga desde el servidor web. Dependiendo de las especificaciones de la configuración del servidor web puede haber diferentes maneras de lograr esto. En común es que el cargador Qt espera encontrar las librerías y plugins Qt en un directorio de nombre "qt", relativo al archivo html que carga la aplicación.
Si ya estás copiando la aplicación al servidor web como parte del despliegue, entonces copiar Qt también es una opción posible. Si usted está sirviendo la aplicación directamente desde su directorio de construcción - a menudo el caso durante las fases de desarrollo - a continuación, crear un enlace simbólico a Qt puede funcionar bien.
Prepárate para el segundo paso creando listas de precarga para componentes Qt como plugins e importaciones Qt Quick. La precarga asegura que todos los componentes Qt necesarios estén disponibles al inicio de la aplicación. La carga retardada, donde los componentes se descargan bajo demanda, también es posible pero no se trata aquí.
La precarga es implementada por el cargador JavaScript de Qt, que descarga archivos desde el servidor web al sistema de archivos en memoria proporcionado por Emscripten. Los archivos a descargar se especifican usando listas de descarga con formato json. Qt proporciona una herramienta para generar listas de precarga, ver la sección Inicio Rápido más arriba.
Problemas conocidos
- No se admiten bucles de eventos anidados. Las aplicaciones no deberían llamar a API como QDialog::exec() y QEventLoop::exec(). Se puede utilizar la función experimental Asyncify.
- No se admite la impresión.
- QDnsLookup lookups y QSsl no funcionan y no están soportados debido al web sandbox. El navegador gestiona las búsquedas DNS y los certificados SSL. DNS sobre HTTP puede ser usado por aplicaciones que necesiten búsquedas dns.
- QTcpSockets puede ser usado pero no todas las funciones POSIX sockets son proxy. Un servidor proxy Websockets como Websockify necesita ser utilizado.
- Todas las clases Q*Server no están soportadas por la plataforma.
- QWebSocket Las conexiones son soportadas por Emscripten sólo en el hilo principal.
- QWebSockets para WebAssembly no soporta el envío de marcos ping o pong, ya que la API disponible para páginas web y navegadores no expone esta funcionalidad.
- Para utilizar QtWebsockets, puede ser necesario establecer el subprotocolo a 'mqtt' para utilizar QtMqtt. Utilice QWebSocketHandshakeOptions cuando abra la página QWebSocket.
- Fuentes: Wasm sandbox no permite el acceso a las fuentes del sistema. Los archivos de fuentes deben distribuirse con la aplicación, por ejemplo en recursos Qt o descargándolos. El propio Qt para WebAssembly incrusta una de estas fuentes.
- Puede haber artefactos de memoria gráfica no inicializada en algunos componentes de Qt Quick Controls 2, como las casillas de verificación. Esto puede verse a veces en pantallas HighDPi.
- Los estilos nativos para Windows y macOS no están soportados ya que Wasm como plataforma no proporciona esa capacidad.
- Error en tiempo de enlace como "wasm-ld: error: initial memory too small", requiere ajustar el tamaño de la memoria inicial. Utilice QT_WASM_INITIAL_MEMORY para establecer el tamaño inicial en kb, que debe ser múltiplo de 64 KB (65536). Por defecto es 50 MB. En CMakeLists.txt: set_target_properties(<target> PROPERTIES QT_WASM_INITIAL_MEMORY "150MB")
- add_executable en CMakeLists.txt no produce <target>.html ni copia qtloader.js. Utilice qt_add_executable en su lugar.
Otros temas
Referencia de opciones de configuración de Qt
Las siguientes opciones de configuración son relevantes a la hora de compilar Qt para WebAssembly desde el código fuente.
| Argumento de configuración | Breve descripción |
|---|---|
| -característica-thread | Wasm multihilo. |
| -feature-wasm-simd128 | Habilita el soporte SIMD de WebAssembly. |
| -feature-wasm-exceptions | Habilita el soporte de excepciones de WebAssembly. |
| -device-option QT_EMSCRIPTEN_ASYNCIFY=1 | Activar el soporte de asyncify. |
| -opción de dispositivo QT_EMSCRIPTEN_ASYNCIFY=2 | Activar el soporte de asyncify (JSPI). |
Qt deshabilita algunas características por defecto para la plataforma WebAssembly, para reducir el tamaño del binario. Puedes habilitar explícitamente una característica cuando configures Qt para WebAssembly:
| Configurar Argumento | Descripción breve |
|---|---|
| -característica-topleveldomain | Permite comprobar si un dominio es de nivel superior. |
Tamaños típicos de descarga
Huella esperada (tamaño de descarga): Los módulos Wasm producidos por el compilador pueden ser grandes, pero se comprimen bien:
| Ejemplo | gzip | brotli |
|---|---|---|
| helloglwindow (QtCore + QtGui) | 2.8M | 2.1M |
| wiggly widget (QtCore + QtGui + QtWidgets) | 4.3M | 3.2M |
| SensorTag (QtCore + QtGui + QtWidgets + QtQuick + QtCharts) | 8.6M | 6.3M |
La compresión se realiza normalmente en el servidor web, utilizando funciones de compresión estándar: el servidor comprime automáticamente o recoge versiones precomprimidas de los archivos. Por lo general, no es necesario un tratamiento especial de los archivos Wasm.
Para más información, consulte Minimizar el tamaño de los binarios.
Ejemplos
Ejemplos y demos de aplicaciones Qt ejecutándose en un navegador web: Demostraciones de Qt
Recursos externos
Licencia
Qt para WebAssembly está disponible bajo licencias comerciales de The Qt Company. Además, está disponible bajo la Licencia Pública General GNU, versión 3. Consulte Licencias de Qt para más detalles.
Véase también https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS y https://developer.mozilla.org/en-US/docs/Web/HTTP/Cross-Origin_Resource_Policy.
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.