目前社区两大Vue+Electron的脚手架:electron-vue和vue-cli-plugin-electron-builder,css
都有这样那样的问题,且都还不支持Vue3,然而Vue3已经是大势所趋,html
Vite势必也将成为官方Vue脚手架,vue
下图是尤雨溪在开发好Vite以后与webpack之父的对话node
因此开发一个Vite+Vue3+Electron的脚手架的需求日趋强烈webpack
我前段时间作了一个,git
可是发现了一些与Vite有关的问题,github
好比:Vite会把开发环境的process对象吃掉的问题web
这对于web项目来讲问题不大,但对于咱们的Electron项目来讲,就影响很大了vue-router
今天我就把这个思路和实现方式的关键代码发出来供你们参考,vue-cli
同时也但愿Vue社区的贡献者们,能注意到这个问题
(给Vue官方的各个项目提issue真的是太难了,Electron官方项目在这方面就作的很好,很open、很包容)
先用Vite建立一个Vue3的工程,这就是你的实际项目工程
接着安装几个Electron相关的依赖,最终个人工程下的依赖状况以下:
"@vue/compiler-sfc": "^3.0.0", "vite": "^1.0.0-rc.9", "vue": "^3.0.2", "vue-router": "^4.0.0-rc.1", "electron": "^11.0.2", "electron-builder": "^22.9.1", "electron-updater": "^4.3.5",
注意:这些依赖所有安装在devDependencies下
各个库的版本发文时应该是最新的了,不过若是有更新的版本,你彻底能够用,没影响。
工程的目录结构大概是以下这样:
[yourProject]
node_modules 依赖包
public vite建立的目录,为vue服务的,实际没多大用
release 打包后编译输出的目录,该目录的根目录下存放打包后的安装包
bundled 该目录存放vue打包后的文件(html js css img等)
win-unpacked 该目录存放编译后生成的可执行文件及相关的dll,不包含安装包
resource 资源目录
unrelease 该目录存放编译期须要的资源
release 该目录存放编译后须要随安装包分发给客户的资源
script 此目录存放各类脚本,好比编译脚本,启动脚本,签名脚本等
src 源码目录
render 渲染进程源码目录
main 主进程源码目录
common 两个进程都会用到的共用源码目录
package.json 项目配置文件
index.html vue3的入口页面
.gitignore
接着在package.json中,增长两个命令:
"scripts": { "start": "node ./script/dev.js", "release": "node ./script/release.js" },
同时在script目录下建立相应的文件,接着咱们就开始撰写者两个文件的代码了
调试脚本首先要作的工做就是启动Vue项目
让它跑在http://localhost下,这样咱们修改渲染进程的代码时,
会经过Vite的热更新机制实时反馈到界面上
Vite除了提供cli的指令启动项目外,也提供了API,我这里就是直接调它的API来启动项目的
关键代码以下:
let vite = require("vite") createServer () { return new Promise((resolve, reject) => { let options = { root:process.cwd(), enableEsbuild: true }; this.server = vite.createServer(options); this.server.on("error", (e) => this.serverOnErr(e)); this.server.on("data", (e) => console.log(e.toString())); this.server.listen(this.serverPort, () => { console.log(`http://localhost:${this.serverPort}`); resolve(); }); }); },
其中this.serverPort是绑定在当前对象上的一个变量,意义是指定vite项目启动时使用的端口号
启动成功后http server对象绑定到当前对象的server变量上
若是启动过程当中报错,则颇有多是端口占用,将执行以下逻辑:
serverOnErr (err) { if (err.code === "EADDRINUSE") { console.log( `Port ${this.viteServerPort} is in use, trying another one...` ); setTimeout(() => { this.server.close(); this.serverPort += 1; this.server.listen(this.viteServerPort); }, 100); } else { console.error(chalk.red(`[vite] server error:`)); console.error(err); } },
这段逻辑就是递增端口号,再次尝试启动http server
每每每一个开发人员的环境变量都是不同的
有的开发人员须要连开发服务器A,有的开发人员须要连开发服务器B
并且开发环境的环境变量、测试环境、生产环境的环境变量也不同
因此我把环境变量设置到几个单独的文件中
方便区分不一样的环境,也方便gitignore,避免不一样开发人员的环境变量互相冲突
开发环境的环境变量保存在src/script/dev.env.js中
let env = require("./dev.env.js")
生产环境的环境变量则为release.env.js
这个文件的代码很是简单,以下:
module.exports = {
APP_VERSION: require("../package.json").version, ENV_NOW: "dev", PROTOBUF_SERVER: "******.com", SENTRY_SERVICE: "https://******.com/34", ELECTRON_DISABLE_SECURITY_WARNINGS: true }
须要注意的是:ELECTRON_DISABLE_SECURITY_WARNINGS,
这个环境变量是为了屏蔽Electron开发者调试工具那一大堆警告的
(你若是开发过Electron应用,你应该知道我说的是什么)
APP_VERSION是从项目的package.json中取的版本号,
你固然能够不设置这个环境变量,经过Electron的API获取版本号
app.getVersion() //主进程可用
但经过ElectronAPI获取到的版本号,在开发环境下,是Electron.exe的版本号,不是你的项目的版本号
打包编译后,这个问题是不存在的。
ENV_NOW是当前的环境,开发环境下它的值为dev,打包编译后的生产环境它的值应为product,
由于如今咱们是讲如何构建开发环境,引用的是dev.env.js,
等下一篇文章讲如何构建编译环境时,引用的就是release.env.js了,
Vite之因此快,有一个很重要的缘由是它使用了esbuild模块来编译代码
这里咱们也使用esbuild来编译咱们的主进程的代码
前面说了主进程是放在src/main/目录下的
这里我使用的是TypeScript开发,入口程序是app.ts,你彻底可使用Js开发,文件名也随你自定义
buildMain () { let outfile = path.join(this.bundledDir, "entry.js"); let entryFilePath = path.join(process.cwd(), "src/main/app.ts"); //这个方法获得的结果:{outputFiles: [ { contents: [Uint8Array], path: '<stdout>' } ]} esbuild.buildSync({ entryPoints: [entryFilePath], outfile, minify: false, bundle: true, platform: "node", sourcemap: false, external: ["electron"], }); env.WEB_PORT = this.serverPort; let envScript = `process.env={...process.env,...${JSON.stringify(env)}};` let js = `${envScript}${os.EOL}${fs.readFileSync(outfile)}`; fs.writeFileSync(outfile, js) },
esbuild会自动查找app.ts引用的其余代码,
还有treeshaking机制保证你不会把无用的代码打包到输出目录
我把sourcemap关掉了,由于调试主进程很困难,
基本都是手动console.log信息调试的,朋友们有好的建议请赐教一下
platform要指定成node,要否则esbuild会尝试帮你去找node.js内置的包,确定找不到,就报错了
同理,还要把electron设置成external
在上一节设置的环境变量的基础上
咱们又增长了一个WEB_PORT的环境变量,
Electron启动后,要根据这个变量去加载localhost的页面,
这个变量是应用启动时肯定的,是动态的,因此没办法设置到dev.env.js中
输出代码前,咱们把环境变量的值也附加在输出代码中了
这样Electron进程启动时,会先设置好环境变量,再执行具体的业务代码
(咱们固然也能够经过其余方式设置环境变量,但这样作主要是为了和生产环境保持一致,看到下一篇文章你就会知道了)
最终生成的代码会被输出到这个目录下面:
bundledDir: path.join(process.cwd(), "release/bundled")
稍后咱们启动Electron时,也会让Electron加载这个目录下的入口程序。
Electron的node module并无提供API给开发者调用以启动进程
因此咱们只能经过node的child_process模块来启动Electron的进程
代码以下:
createElectronProcess () { this.electronProcess = spawn( require("electron").toString(), [path.join(this.bundledDir, "entry.js")], { cwd: process.cwd(), env, } ); this.electronProcess.on("close", () => { this.server.close(); process.exit(); }); this.electronProcess.stdout.on("data", (data) => { data = data.toString(); console.log(data); }); },
require("electron").toString()获得的是Electron的可执行文件的路径
Windows环境下为:node_modules\electron\dist\electron.exe
Mac环境下为:node_modules/electron/dist/Electron.app/Contents/MacOS/Electron
path.join(this.bundledDir, "entry.js")为Electron进程指定了入口程序文件的地址
cwd: process.cwd()是为Electron指定当前工做目录(此处又为Electron指定了一次环境变量,其实不指定也不要紧)
当Electron进程退出时,咱们也关闭了Vite建立的http server
此处最关键的逻辑就是这一句
if (process.env.ENV_NOW === "dev") { await win.loadURL(`http://localhost:${process.env.WEB_PORT}/`); }
process.env.WEB_PORT就是咱们上文中设置的WEB_PORT变量
这个逻辑固然还有else分支,那是下一篇博文的内容了
敬请期待!