You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Qt WebEngineView中Python调用JS函数及JS模块导入问题

Python调用ES模块中JS函数的解决方案

问题描述

我通过QWebEngineView实现Python加载嵌入JS文件的HTML页面,需要满足两个需求:

  1. utils.js和main.js之间能互相导入函数
  2. Python端可以调用main.js中的函数

实际运行时遇到问题:在main.js中导入utils.js后,Python调用main()函数时会报错Uncaught ReferenceError: main is not defined。直接在HTML中导入所有JS文件能解决,但这种方式不够灵活。

项目结构

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

现有代码

main.py

import sys
from os.path import dirname
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtWebEngineCore import *
from PyQt5.QtWebEngineWidgets import *


application_path = (dirname(sys.executable) if getattr(sys, "frozen", False) else 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 WebEnginePage(QWebEnginePage):
    def javaScriptConsoleMessage(self, level, message, lineNumber, sourceID):
        print(f"[JS Message] Line-{lineNumber}:", message)


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

    def onLoad(self):
        self.browser.page().runJavaScript('main()')


if __name__ == "__main__":
    scheme = QWebEngineUrlScheme(b"qt")
    scheme.setFlags(QWebEngineUrlScheme.CorsEnabled)
    QWebEngineUrlScheme.registerScheme(scheme)
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

main.js

import { sayHello } from "./utils.js"

function main(){
    console.log('main')
}

sayHello();

utils.js

export function sayHello() {
    console.log('Message from utils.js');
}

init.html

<body>
    <!-- <script src="js/utils.js" type="module"></script> -->
    <script src="js/main.js"></script>
    <h1>Hello World</h1>
</body>

解决方案

核心原因

使用import/export的JS文件会被当作ES模块,模块内的变量和函数默认处于私有作用域,不会暴露到全局。而Python的runJavaScript是在全局作用域执行代码,因此无法找到模块内的main函数。


方案1:ES模块+全局挂载(推荐)

这种方式既保留ES模块的模块化能力,又将需要Python调用的函数暴露到全局。

  1. 修改main.js,将main函数挂载到window全局对象:
import { sayHello } from "./utils.js"

function main(){
    console.log('main')
}

// 将函数暴露到全局作用域
window.main = main;

sayHello();
  1. 修改init.html,给main.js的script标签添加type="module",告知浏览器这是ES模块:
<body>
    <script src="js/main.js" type="module"></script>
    <h1>Hello World</h1>
</body>

修改后,Python端调用main()就能正常执行,同时main.js可以正常导入utils.js的函数。


方案2:IIFE模拟模块(兼容旧场景)

如果不想使用ES模块,可以用立即执行函数表达式(IIFE)组织代码,同时将函数挂载到全局。

  1. 修改main.js:
(function() {
    // 加载utils.js
    const utilsScript = document.createElement('script');
    utilsScript.src = './utils.js';
    document.head.appendChild(utilsScript);

    // 脚本加载完成后定义并暴露main函数
    utilsScript.onload = function() {
        function main(){
            console.log('main')
            sayHello();
        }
        window.main = main;
    }
})();
  1. 修改utils.js,将函数挂载到全局:
function sayHello() {
    console.log('Message from utils.js');
}
window.sayHello = sayHello;
  1. init.html无需添加type="module",保持原有脚本引用即可。

优化建议

为避免全局变量冲突,建议自定义命名空间来挂载函数,例如:

// main.js中
window.MyApp = window.MyApp || {};
window.MyApp.main = main;

// Python调用时改为
self.browser.page().runJavaScript('MyApp.main()')

内容的提问来源于stack exchange,提问作者Mohammad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 06:34:59