Qt WebEngineView中Python调用JS函数及JS模块导入问题
Python调用ES模块中JS函数的解决方案
问题描述
我通过QWebEngineView实现Python加载嵌入JS文件的HTML页面,需要满足两个需求:
utils.js和main.js之间能互相导入函数- 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调用的函数暴露到全局。
- 修改
main.js,将main函数挂载到window全局对象:
import { sayHello } from "./utils.js" function main(){ console.log('main') } // 将函数暴露到全局作用域 window.main = main; sayHello();
- 修改
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)组织代码,同时将函数挂载到全局。
- 修改
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; } })();
- 修改
utils.js,将函数挂载到全局:
function sayHello() { console.log('Message from utils.js'); } window.sayHello = sayHello;
init.html无需添加type="module",保持原有脚本引用即可。
优化建议
为避免全局变量冲突,建议自定义命名空间来挂载函数,例如:
// main.js中 window.MyApp = window.MyApp || {}; window.MyApp.main = main; // Python调用时改为 self.browser.page().runJavaScript('MyApp.main()')
内容的提问来源于stack exchange,提问作者Mohammad
相关产品推荐
相关产品推荐

