作者: Jim Wang 公众号: 巴博萨船长

摘要:如何搭建Vue3 + TypeScript + Electron开发环境?

Abstract: How to build Vue3 + TypeScript + Electron development environment?

作者: Jim Wang 公众号: 巴博萨船长

开发环境搭建

开发环境搭建可以使用electron-vite或者手动如下完成(手动自由度高)。

创建Vue3 + TypeScript 项目

项目根目录运行如下命令:

1
npm create vite@latest backend2b

然后选择vue框架,如下:

1
2
3
4
5
6
7
8
9
10
11
12
create-vite@5.2.3
Ok to proceed? (y)
? Select a framework: » - Use arrow-keys. Return to submit.
Vanilla
> Vue
React
Preact
Lit
Svelte
Solid
Qwik
Others

然后选择如下变种:

1
2
3
4
5
? Select a variant: » - Use arrow-keys. Return to submit.
> TypeScript
JavaScript
Customize with create-vue ↗
Nuxt ↗

创建成功,提示如下:

1
2
3
4
5
Done. Now run:

cd backend2b
npm install
npm run dev

然后运行:

1
npm install

成功创建项目之后修改配置文件tsconfig.js,

  • 将target修改为esnext
  • 将module修改为commonjs
  • 将lib中使用esnext替换原有的版本库
1
2
3
4
5
6
7
"compilerOptions": {
"target": "esnext",
"useDefineForClassFields": true,
"module": "commonjs",
"lib": ["esnext", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
...

修改原因如下:

  • target: 它定义了你的TypeScript代码将编译为哪个版本的JavaScript。常见的选项有"es5""es6""es2015"等。如果你的目标是在现代浏览器或者Electron环境中运行,选择"es5"或更高版本通常是合适的。例如,为了支持Electron的最新版本,你可能会选择"es6"或更高。
  • module: 它定义了模块系统的类型。常见的选项有"commonjs""esnext"。在Electron项目中,通常使用"commonjs",因为Electron主进程和渲染进程通常使用CommonJS模块系统。

最后运行如下命令检查项目配置:

1
2
3
4
5
6
7
8
9
10
11
...backend2b>npm run dev

> backend2b@0.0.0 dev
> vite


VITE v5.2.8 ready in 685 ms

➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h + enter to show help

将electron引入项目

安装electron

1
npm install electron

安装concurrently与wait-on

1
npm install -D concurrently wait-on
  • concurrently:阻塞运行多个命令,-k参数用来清除其它已经存在或者挂掉的进程
  • wait-on:等待资源,此处用来等待url可访问

安装cross-env和electron-builder

1
npm install -D cross-env electron-builder
  • cross-env: 该库让开发者只需要注重环境变量的设置,而无需担心平台设置
  • electron-builder: electron打包库

修改配置文件vite.config.ts,将其名称改为vite.config.mts以避免警告“The CJS build of Vite’s Node API is deprecated”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import * as path from 'path'

// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
base: './',
resolve: {
alias: [{ find: '@', replacement: path.resolve(__dirname, 'src') }],
},
server: {
hmr: {
overlay: false
}
}
})

修改ts编译配置文件tsconfig.json,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
{
"compilerOptions": {
"target": "esnext",
"useDefineForClassFields": true,
"module": "commonjs",
/*忽略所有的声明文件( *.d.ts)的类型检查。*/
"lib": ["esnext", "dom"],
"skipLibCheck": true,
"outDir": "dist/electron",

/* Bundler mode */
"moduleResolution": "node",
"noEmit": true,
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"jsx": "preserve",

/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,

/* 生成相应的map文件 */
"sourceMap": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
}
},
"include": ["src/**/*"],
"references": [{ "path": "./tsconfig.node.json" }]
}

再修改tsconfig.node.json文件,修改成如下内容,注意include中的文件名称,

1
2
3
4
5
6
7
8
9
10
11
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true
},
"include": ["vite.config.mts"]
}

在src文件夹内创建子文件夹electron,然后添加main.ts文件,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import { join } from 'path';
import {
app,
BrowserWindow
} from 'electron';

const isDev = process.env.npm_lifecycle_event === "app:dev" ? true : false;


function createWindow() {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 1360,
height: 800,
webPreferences: {
preload: join(__dirname, 'preload.js'),
},
});

// and load the index.html of the app.
if (isDev) {
mainWindow.loadURL('http://localhost:5173');// Open the DevTools.
mainWindow.webContents.openDevTools();
} else {
mainWindow.loadFile(join(__dirname, '../index.html'));
}
// mainWindow.loadURL( //this doesn't work on macOS in build and preview mode
// isDev ?
// 'http://localhost:3000' :
// join(__dirname, '../../index.html')
// );
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
});

// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});

在同一目录下添加preload.ts文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// All of the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.
import { contextBridge, ipcRenderer } from 'electron'
window.addEventListener('DOMContentLoaded', () => {
const replaceText = (selector: any, text: any) => {
const element = document.getElementById(selector)
if (element) element.innerText = text
}

for (const dependency of ['chrome', 'node', 'electron']) {
replaceText(`${dependency}-version`, process.versions[dependency])
}
})

contextBridge.exposeInMainWorld('electronAPI', {
openFile: () => ipcRenderer.invoke('dialog:openFile')
})

首先在package.json,添加如下内容,

1
2
"author": "author name",  
"main": "dist/electron/main.js",

然后添加运行cli,修改package.json文件的内容,如下:

1
2
3
4
5
6
7
8
9
10
11
12
"scripts": {
"vite:dev": "vite",
"vite:build": "vue-tsc --noEmit && vite build",
"vite:preview": "vite preview",
"ts": "tsc",
"watch": "tsc -w",
"lint": "eslint -c .eslintrc --ext .ts ./src",
"app:dev": "tsc && concurrently vite \" electron .\" \"tsc -w\"",
"app:build": "npm run vite:build && tsc && electron-builder build --win",
"app:preview": "npm run vite:build && tsc && cross-env NODE_ENV=development electron ."
}
...

再将package.json添加build属性,内容如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
"build": {
"appId": "AppId",
"productName": "ProductName",
"copyright": "Copyright © 2022 <your-name>",
"asar": true,
"directories": {
"buildResources": "assets",
"output": "release/${version}"
},
"files": [
"dist"
],
"mac": {
"artifactName": "${productName}_${version}.${ext}",
"target": [
"dmg"
]
},
"win": {
"target": [
{
"target": "nsis",
"arch": [
"x64"
]
}
],
"artifactName": "${productName}_${version}.${ext}"
},
"nsis": {
"oneClick": false,
"perMachine": false,
"allowToChangeInstallationDirectory": true,
"deleteAppDataOnUninstall": false
}
},

修改env.d.ts或者vite-env.d.ts

1
2
3
4
5
6
7
8
9
/// <reference types="vite/client" />

declare module '*.vue' {
import type { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}


版权声明:
文章首发于 Jim Wang's blog , 转载文章请务必以超链接形式标明文章出处,作者信息及本版权声明。