技术栈:Electron + Vite2 + Vue3 + TypeScript,线程池用的 Piscina。
一个简单的 Piscina 线程池使用例子
使用 Piscina 线程池至少需要两个文件:
- 创建 Piscina 实例、分发任务的主线程文件,可以用 TypeScript 写
- 执行任务的 Worker 文件,暂时用 mjs 写的,方便,没有 bug
import Piscina from 'piscina'
const piscina = new Piscina({ filename: 'listdir.mjs' })
const result = await piscina.run({a: 2, b: 3})
在上面 piscina.ts 同一目录下新建 Worker:
export default function ({a, b}) {
return a + b
}
在实际项目中,应用启动时的目录一般是项目根目录,这样构建 Piscina 实例时的filename
实际上是不存在的。
开发时使用 Piscina 线程池
需要将 filename 改成相对于项目根目录的路径,我们可以将 Worker 放在 electron/main/worker 目录下:
electron/main/worker/worker.mjs
在启动开发服务器时,我们需要将这个文件单独复制到 Electron 打包目录中:
dist/electron/main/worker.mjs
因此需要安装一个插件 rollup-plugin-copy:
ni -D rollup-plugin-copy
然后配置插件:
import copy from 'rollup-plugin-copy'
copy({
targets: [
{
src: 'electron/main/worker/*.mjs',
dest: 'dist/electron/main/',
},
]
})
一般开发服务器热刷新时都会先删除 dist 目录,这里有一个问题是,如果使用下面的命令将整个 dist 目录删除:
rmSync('dist', { recursive: true, force: true })
因为 dest 目录不存在,复制工作将不能完成。因此我们需要保留这个目录,并且将其他内容全部删掉:
import { rmSync, readdirSync } from 'fs'
const distDir = 'dist/electron'
readdirSync(distDir).forEach(k => {
// "dist/electron/main" required for copying workers
if (k === 'main') {
rmSync(join(distDir, k, '*'), { recursive: true, force: true })
} else {
rmSync(join(distDir, k), { recursive: true, force: true })
}
})
所以一个完整的插件配置是:
import { defineConfig } from 'vite'
import { rmSync, readdirSync } from 'fs'
import copy from 'rollup-plugin-copy'
const distDir = 'dist/electron'
readdirSync(distDir).forEach(k => {
// "dist/electron/main" required for copying workers
if (k === 'main') {
rmSync(join(distDir, k, '*'), { recursive: true, force: true })
} else {
rmSync(join(distDir, k), { recursive: true, force: true })
}
})
export default defineConfig({
plugins: [
copy({
targets: [
{
src: 'electron/main/worker/*.mjs',
dest: 'dist/electron/main/',
},
]
})
]
})
这样我们就能看到 Electron 打包后目录结构:
- dist
- electron
- main
- index.js
- worker.mjs
- preload
- index.js
- main
- electron
然后将这个 dist/electron/main/worker.mjs 路径传递给 Piscina:
import Piscina from 'piscina'
import { join, resolve } from 'path'
const getWorkerPath = (worker: string) => {
if (process.env.NODE_ENV === 'development') {
return resolve(join('dist', 'electron', 'main', worker))
}
}
const piscina = new Piscina({
filename: getWorkerPath('listdir.mjs'),
})
const result = await piscina.run({a: 2, b: 3})
上述方案解决了开发服务器下使用 Piscina 的问题,那么生产环境呢?
生产时使用 Piscina 线程池
dist 目录是用来存放打包后资源的地方,Electron 打包分为两部分:
- 主线程打包,这个由 vite 每次热更新时自动打包完成,位置是 dist/electron
- 页面打包,这部分是纯前端的打包,位置是 dist/
执行应用打包(nr build
)时分为三步:
vue-tsc --noEmit
检查 TypeScriptvite build
打包前端代码electron-builder
打包整个应用
electron-builder 默认将整个 dist 下的内容打包到 asar 文件中,这就导致我们的 worker.mjs 也被放到了 app.asar 文件中,那么我们就不能在文件系统中访问到 worker.mjs 了。为了解决这个问题,我们可以
-
禁用 asar,当然官方不推荐这种做法
-
设置不打包指定文件,具体的
{ "asar": true, "asarUnpack": [ "./dist/electron/main/worker.mjs" ] }
如果使用第一种方法,worker.mjs 的位置是 resources/app/dist/electron/main/worker.mjs
如果使用第二种方法,worker.mjs 的位置是 resources/app.asar.unpacked/dist/electron/main/worker.mjs
同时修改 Worker 路径搜索函数:
const getWorkerPath = (worker: string) => {
let p = join('dist', 'electron', 'main', worker)
if (process.env.NODE_ENV !== 'development') {
// disable asar when building electron
if (existsSync(p)) p = join('resources', 'app', p)
// enable asar when building electron
else p = join('resources', 'app.asar.unpacked', p)
}
return resolve(p)
}
over
评论区