ArcherGu / fast-vite-nestjs-electron

Vite + Vue + Electron + Nestjs with esbuild, crazy fast! ⚡

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

electron 内调用其他进程,会导致窗口无法正常关闭

edwinhuish opened this issue · comments

请查看示例:edwinhuish/fast-vite-nestjs-electron

代码修改部分:

代码里进加入了 playwright,并通过ipc通讯,调用playwright 打开 chromium。

运行环境

  • Win10 + WSL2 + WSLG

复现步骤:

  • clone 代码库到本地
  • 安装依赖
  • pnpm run dev / pnpm run build 并启动 AppImage
  • 点击启动后的窗口里的 open chromium 按钮
  • 尝试关闭主程序。 无论是chromium是否已关闭,electron 的主窗口的关闭已经失效。

具体问题

  • 开发模式下, 关闭按钮直接失效,同时窗口点击无效。
  • 打包成 AppImage 后,关闭按钮点击一次关闭第一个 chromium,并弹出一个新的 BrowserWindow。 点击关闭第二个 BrowserWindow 的关闭按钮关闭第二个 chromium 。。。 以此类推。。
  • 无论开发环境还是打包好的 AppImage,只要打开过chromium,无论后面有没有手动关闭 chromium,问题依旧。

其他库执行相同操作

测试了一下,应该是你没有真正的关闭 chromium 进程。

根据 playwright 的文档,使用 browser.close() 关闭进程。

  @IpcHandle('open-web')
  public async handleOpenWeb(url: string): Promise<void> {
    const browser = await chromium.launch({ headless: false })
    const context = await browser.newContext()
    const page = await context.newPage()

    await page.goto(url)

    setTimeout(() => {
      // Close
      browser.close()
    }, 5000)
  }

你可以看一下你的系统的任务管理器,比如 top,可以比较方便的排查到后台的进程状况。

测试了一下,应该是你没有真正的关闭 chromium 进程。

根据 playwright 的文档,使用 browser.close() 关闭进程。

  @IpcHandle('open-web')
  public async handleOpenWeb(url: string): Promise<void> {
    const browser = await chromium.launch({ headless: false })
    const context = await browser.newContext()
    const page = await context.newPage()

    await page.goto(url)

    setTimeout(() => {
      // Close
      browser.close()
    }, 5000)
  }

你可以看一下你的系统的任务管理器,比如 top,可以比较方便的排查到后台的进程状况。

其实从问题出现的形式,已经可以基本断定是子进程没正常关闭导致的。但重点不在这里,重点是,主窗口关闭按道理是应该关闭所有子进程。而不是因为子进程未退出,而导致无法关闭,甚至无响应,或者是关闭主程序变成关闭子进程,还打开另外一个新的主窗口。。。

刚才测试了一下草鞋兄的 electron-vite/electron-vite-vue,我在 WSL 环境下也测试出了相同的问题。在使用 playwright 打开 chromium,然后关闭 chromium 窗口,然后尝试关闭 electron 的窗口,失败了,窗口没有响应了,只能手动去 kill。

我在windows的原生环境下测试了一下,虽然关闭 chromium 窗口后,它的子进程依然残留,但是 electron 窗口是可以关闭的,也许 WSL 也是存在兼容性问题。

或许你应该去 playwright 问问。

通过对比发现,将playwright放入 devDependencies 就不会出现这种问题。但 Doubleshot 不支持从 devDependencies 里按需加载。。。

你说的按需加载是指在构建的时候把 playwright 从打包里剔除出去是吧?这个你只要在 VitePluginDoubleshot 的配置中加上 external: ['playwright'] 就行了,它会在构建的时候将 playwright 剔除,回头直接从 node_modules 中加载。

你说的按需加载是指在构建的时候把 playwright 从打包里剔除出去是吧?这个你只要在 VitePluginDoubleshot 的配置中加上 external: ['playwright'] 就行了,它会在构建的时候将 playwright 剔除,回头直接从 node_modules 中加载。

不是的,vite 有个功能,就是以解构的形式去import的话,它打包只会打包引入部分,而不是整个库打包进来。

构建的时候,我还是需要 playwright的,所以不能整个库去掉。

你说的是 treeshake,doubleshot 使用的 tsup(内置 esbuild),默认情况下构建的是 nodejs/electron 应用,而不是前端的那种打包结果,所以在构建的时候是不会把依赖打包进去的,在最终 electron build 时, node_modules 中的 dependencies 相关的依赖会一起放进最终的应用里。所以在node/electron完整支持 ESM前,没法做treeshake,毕竟这个不是前端项目,你最终的构建结果和electron框架比起来其实不会很多。

你理解下 doubleshot 的原理就知道了,虽然提供了 VitePluginDoubleshot 这样的方案来帮助你快速集成到 vite 中,但是实际上renderer 的构建和 main 的构建是分开来的,doubleshot处理的是 main 的构建。

更加符合 doubleshot 使用的模板应该是这个:template-vue-nest

嗯,这个原理我在前两天看你的源码已经了解得7788。设计理念上比其他的插件好,这个也是我用它的原因。但是建议 treeshake 还是有必要加入的,毕竟这个是桌面应用,不是后端框架,桌面应用缩减体积还是很有用的。

playwright 这个问题,查过很多文章,找不到解决方案,晚点我试试用 electron-vite/electron-vite-vue 看能否通过 treeshake 解决这个问题。

如果最终还是不行,那我只能将 nestjs 独立成为一个新进程,但整体复杂度会高很多。。

实际上,如果你想在最终构建的项目中使用 playwright (当然,我不是很理解这种做法,playwright毕竟是一个dev用的库,为什么放在最终的产品中?),你就一定要放在 dependencies 中,但是因为 playwright 是一个nodejs的库(里面估计有不少原生的addon),所以在electron最终build后可能会有兼容性的问题,因此你需要用 electron-rebuild 或者 "postinstall": "electron-builder install-app-deps" 来处理这些原生依赖模块,尝试下。

实际上,如果你想在最终构建的项目中使用 playwright (当然,我不是很理解这种做法,playwright毕竟是一个dev用的库,为什么放在最终的产品中?),你就一定要放在 dependencies 中,但是因为 playwright 是一个nodejs的库(里面估计有不少原生的addon),所以在electron最终build后可能会有兼容性的问题,因此你需要用 electron-rebuild 或者 "postinstall": "electron-builder install-app-deps" 来处理这些原生依赖模块,尝试下。

因为我是需要playwright来模拟一些网页操作,用于简化用户的实际操作。需要模拟的网页不属于我的,所以我无权直接调用他们的api,只能通过网页操作模拟。

"postinstall": "electron-builder install-app-deps" 这个命令我一直都有配置。

如果你硬要使用 treeshake,其实已经是支持的了,简单的做法是把 dependencies 中的依赖放到 devDependencies,这样在转译时就会自动把项目所需的依赖都打包一起,然后 esbuild 默认是支持 treeshake 的,这部分它会处理。

但是我不保证可用性,因为 treeshake 说实话是属于前端的东西,就像你看看 nestjs源码的构建结果,它也是排除掉它的依赖的,所以如果你要在node/electron 做 treeshake,首先,必须让node/electron非常良好的支持ESM,然后这是一个循环遍历所有依赖(包括依赖的依赖)的过程,接着这些依赖又可能包含了原生的addon,我觉得应该是会失败的。

实际上,如果你想在最终构建的项目中使用 playwright (当然,我不是很理解这种做法,playwright毕竟是一个dev用的库,为什么放在最终的产品中?),你就一定要放在 dependencies 中,但是因为 playwright 是一个nodejs的库(里面估计有不少原生的addon),所以在electron最终build后可能会有兼容性的问题,因此你需要用 electron-rebuild 或者 "postinstall": "electron-builder install-app-deps" 来处理这些原生依赖模块,尝试下。

因为我是需要playwright来模拟一些网页操作,用于简化用户的实际操作。需要模拟的网页不属于我的,所以我无权直接调用他们的api,只能通过网页操作模拟。

"postinstall": "electron-builder install-app-deps" 这个命令我一直都有配置。

那就试试 electron-rebuild,这个可能会更加好点。

嗯,这个原理我在前两天看你的源码已经了解得7788。设计理念上比其他的插件好,这个也是我用它的原因。但是建议 treeshake 还是有必要加入的,毕竟这个是桌面应用,不是后端框架,桌面应用缩减体积还是很有用的。

playwright 这个问题,查过很多文章,找不到解决方案,晚点我试试用 electron-vite/electron-vite-vue 看能否通过 treeshake 解决这个问题。

如果最终还是不行,那我只能将 nestjs 独立成为一个新进程,但整体复杂度会高很多。。

我觉得你还是别试了,electron最后需要的也是CJS的代码,treeshake需要ESM支持,这个是标准规范上的问题,在node/electron完整支持ESM前,treeshake估计是很难做到。

我先逛逛 playwright 的github issues,看看能否找到解决方法。。。

我应该已经找到了解决方案。ChatGPT 的答案虽然有错误,但是改改应该能运行成功。

(QELNF%4S2~@EV({722(}PX

你这不是在另外一个进程里启动嘛😮‍💨

按需引入是做不到的,或者说难度非常高,毕竟是commonjs

是的,在另外一个进程启动的话,应该就不会影响electron主进程了。除了启动server的 js 文件,其他代码可以写在electron的nestjs框架里,实际操作和直接在 electron 启动,是一样的。

@ArcherGu 这个问题已经解决了。 可以看我最新的示例代码的 commit。

关键点在这里:FIX: 有进程未关闭之前无法正常关闭窗口

当点击关闭按钮的时候,由于没有其他electron窗口了,触发 window-all-closed 事件,进而执行 app.quit()

但是由于还有其他进程,导致代码一直卡在 app.quit(),进而出现之前的那些诡异问题。

最优的解决方案应该是 window-all-closed 直接杀主进程,但是我查了很久资料,没看到有杀进程的方式。 app.quit() , app.exit(), process.exit() 均无效。

所以当前的解决方案只能是手动关闭未结束的进程,所以我将 window-all-closed 事件移动到了可执行退出 playwright 的位置。

如果有其他更优的解决方案,烦请告知。