PyQt5 How To Use JavaScript Modules

Issue

In my Application I create a window with PyQt5 and load an HTML source. I want to use ThreeJS in my project which requires the use of JavaScript modules. However I do not seem to get modules working in my setup:

this works fine: <script src="js/main.js"></script>

this does not: <script type="module" src="js/main.js"></script>

What’s the problem here? Do I need to do something additional in PyQt5 to enable JavaScript modules.

Without JavaScript modules it will be very hard to use ThreeJS, so how can I solve this issue?

EDIT: Here is the PyQt5 code that I’m using:

import sys
import os
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtWebEngineWidgets import *

class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.browser = QWebEngineView()
        config_name = 'data_files/init.html'
        if getattr(sys, 'frozen', False):
            application_path = os.path.dirname(sys.executable)
        elif __file__:
            application_path = os.path.dirname(__file__)
        config_path = os.path.join(application_path, config_name)
        self.browser.load(QUrl().fromLocalFile(config_path))
        self.setCentralWidget(self.browser)
        self.showMaximized()


app = QApplication(sys.argv)
QApplication.setApplicationName('v0.1')
window = MainWindow()
app.exec_()

This is the html:

<body>
    <script src="js/main.js"></script>
    <div id="myText" style="opacity:0">Hello World</div>
</body>¨

This is the javaScript:

window.addEventListener("load", () => {
    document.getElementById("myText").style.opacity = "1"
})

The JavaScript only works when the script is not a Module, when using type="module" nothing happens, the opacity of my element doesn’t change.

Solution

Explanation:

As the docs points out:

You need to pay attention to local testing — if you try to load the HTML file locally (i.e. with a file:// URL), you’ll run into CORS errors due to JavaScript module security requirements. You need to do your testing through a server.

The modules are not accessible if you use local files, therefore you get the error message:

js: Failed to load module script: The server responded with a non-JavaScript MIME type of "". Strict MIME type checking is enforced for module scripts per HTML spec.

Solution:

In this case there are 2 options:

1. local server

Implement a server, for example using aiohttp + qasync:

import asyncio
import functools
import os
from pathlib import Path
import sys

from aiohttp import web

from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWebEngineWidgets import QWebEngineView

import qasync
from qasync import QApplication


application_path = (
    Path(sys.executable).resolve().parent
    if getattr(sys, "frozen", False)
    else Path(__file__).resolve().parent
)


class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.browser = QWebEngineView()

        config_name = "/data_files/init.html"
        url = QUrl("http://localhost:4000")
        url.setPath(config_name)
        self.browser.load(url)
        self.setCentralWidget(self.browser)
        self.showMaximized()


async def main():
    def close_future(future, loop):
        loop.call_later(10, future.cancel)
        future.cancel()

    loop = asyncio.get_event_loop()
    future = asyncio.Future()

    qt_app = QApplication.instance()
    if hasattr(qt_app, "aboutToQuit"):
        getattr(qt_app, "aboutToQuit").connect(
            functools.partial(close_future, future, loop)
        )

    app = web.Application()
    app.router.add_static("/", application_path, show_index=True)
    runner = web.AppRunner(app)
    await runner.setup()
    site = web.TCPSite(runner, "localhost", 4000)
    await site.start()

    view = MainWindow()
    view.show()

    await future
    await runner.cleanup()
    return True


if __name__ == "__main__":
    try:
        qasync.run(main())
    except asyncio.exceptions.CancelledError:
        sys.exit(0)

2. Custom QWebEngineUrlSchemeHandler

import sys
import os
from PyQt5.QtCore import QCoreApplication, QUrl, QFile, QFileInfo, QMimeDatabase
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtWebEngineCore import (
    QWebEngineUrlScheme,
    QWebEngineUrlSchemeHandler,
    QWebEngineUrlRequestJob,
)
from PyQt5.QtWebEngineWidgets import QWebEngineView


application_path = (
    os.path.dirname(sys.executable)
    if getattr(sys, "frozen", False)
    else os.path.dirname(__file__)
)


class QtSchemeHandler(QWebEngineUrlSchemeHandler):
    def requestStarted(self, job):
        request_method = job.requestMethod()
        if request_method != b"GET":
            job.fail(QWebEngineUrlRequestJob.RequestDenied)
            return

        request_url = job.requestUrl()
        request_path = request_url.path()
        file = QFile(application_path + request_path)
        file.setParent(job)
        job.destroyed.connect(file.deleteLater)
        if not file.exists() or file.size() == 0:
            print(f"resource '{request_path}' not found or is empty")
            job.fail(QWebEngineUrlRequestJob.UrlNotFound)
            return

        file_info = QFileInfo(file)
        mime_database = QMimeDatabase()
        mime_type = mime_database.mimeTypeForFile(file_info)
        job.reply(mime_type.name().encode(), file)


class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.browser = QWebEngineView()
        self.scheme_handler = QtSchemeHandler()
        self.browser.page().profile().installUrlSchemeHandler(
            b"qt", self.scheme_handler
        )
        filename = "/data_files/init.html"
        url = QUrl("qt://main")
        url.setPath(filename)
        self.browser.load(url)
        self.setCentralWidget(self.browser)


if __name__ == "__main__":

    scheme = QWebEngineUrlScheme(b"qt")
    scheme.setFlags(QWebEngineUrlScheme.CorsEnabled)
    QWebEngineUrlScheme.registerScheme(scheme)
    app = QApplication(sys.argv)
    QApplication.setApplicationName("v0.1")
    window = MainWindow()
    window.show()
    app.exec_()

File structure:

├── data_files
│   ├── init.html
│   └── js
│       └── main.js
└── main.py

Answered By – eyllanesc

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published