使用 Vue、Electron 以及 Go 建立一個小工具
前言
最近剛好有機會要寫有圖形化介面的程式。想來想去感覺可以寫寫看 Electron!於是乎本篇就這樣誕生了。
主要內容
建立 Vue 專案
1yarn create vite test-001 --template vue
2cd test-001
3yarn
4yarn dev
加入路徑別名
把專案中的 src
加上別名 @
。
1mv vite.config.js vite.config.cjs.bk
2
3cat << EOF > vite.config.cjs
4import { defineConfig } from 'vite'
5import Vue from '@vitejs/plugin-vue'
6import path from 'path'
7
8// https://vitejs.dev/config/
9export default defineConfig({
10 plugins: [
11 Vue(),
12 ],
13 resolve: {
14 alias: {
15 '@': path.resolve(__dirname, './src'),
16 },
17 },
18})
19EOF
安裝及設定 Tailwindcss
1yarn add -D tailwindcss postcss autoprefixer
2cat << EOF > postcss.config.cjs
3module.exports = {
4 plugins: {
5 tailwindcss: {},
6 autoprefixer: {},
7 }
8}
9EOF
10
11cat << EOF > tailwind.config.cjs
12module.exports = {
13 content: [
14 "./index.html",
15 "./src/**/*.{vue,js,ts,jsx,tsx}",
16 ],
17 theme: {
18 extend: {},
19 },
20 plugins: [],
21}
22EOF
23
24mkdir -p src/styles
25
26cat << EOF > src/styles/base.css
27@layer base {
28 html, body, #app {
29 @apply h-full w-full;
30 }
31 body, #app {
32 @apply bg-[#333333];
33 @apply font-light;
34 }
35}
36EOF
37
38cat << EOF > src/styles/components.css
39@layer components {
40 * {
41 @apply outline outline-1 outline-red-500;
42 }
43}
44EOF
45
46cat <<EOF > src/styles/index.css
47@import "tailwindcss/base";
48@import "base.css";
49
50@import "tailwindcss/components";
51@import "components.css";
52
53@import "tailwindcss/utilities";
54EOF
55
56sed -i "3iimport '@/styles/index.css'" src/main.js
57
58sed -i '/<\/template>/i \ \ \<h1\ class=\"text-red-200\"\>Hello\ world\<\/h1\>' src/App.vue
加入 Unplugin Icons
1
2yarn add --dev unplugin-vue-components unplugin-icons @iconify/json
3
4mkdir -p src/components
5sed -i "4iimport Components from 'unplugin-vue-components/vite'" vite.config.cjs
6sed -i "4iimport IconsResolver from 'unplugin-icons/resolver'" vite.config.cjs
7sed -i "4iimport Icons from 'unplugin-icons/vite'" vite.config.cjs
8
9sed -i '/plugins:/a \ \ \ \ \ Components({\
10 dirs: ["src/components"],\
11 resolvers: [\
12 IconsResolver({\
13 prefix: false,\
14 enabledCollections: ["mdi"],\
15 })\
16 ],\
17 }),\
18 Icons(),' vite.config.cjs
19
20sed -i '/<\/template>/i \ \ \<mdi-account-heart\ \/\>' src/App.vue
加入 Vue Router 和 vite-plugin-pages
1yarn add vue-router@4
2yarn add --dev vite-plugin-pages
3
4mkdir -p src/router
5
6cat << EOF > src/router/index.js
7import { createRouter, createWebHistory, createWebHashHistory } from "vue-router";
8import routes from "~pages";
9
10const router = createRouter({
11 // refer to: https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/commonIssues.html
12 history: import.meta.env.PROD ? createWebHashHistory() : createWebHistory(import.meta.env.BASE_URL),
13 routes,
14});
15
16export default router;
17EOF
18
19mkdir -p src/pages
20sed -i "4iimport Pages from 'vite-plugin-pages'" vite.config.cjs
21sed -i '/plugins:/a \ \ \ \ \ Pages({\
22 dirs: [\
23 { dir: "src/pages", baseRoute: "" },\
24 ],\
25 }),' vite.config.cjs
26
27sed -i "3iimport Router from './router'" src/main.js
28sed -i 's/createApp(App)/createApp(App).use(Router)/' src/main.js
29
30cat << EOF > src/App.vue
31<template>
32 <div class="w-full h-full p-1">
33 <RouterView></RouterView>
34 </div>
35</template>
36EOF
37
38cat << EOF > src/pages/index.vue
39<template>
40 <div class="flex w-full h-full rounded overflow-hidden">
41 <div :class="[
42 'transition-all duration-500 ease-out',
43 'h-full w-0 lg:block lg:w-2/12 flex-shrink-0',
44 'max-w-xs'
45 ]"></div>
46 <div class="flex h-full w-full">
47 <img src="@/assets/vue.svg" class="w-1/4" alt="Vue logo" />
48 <div class="flex flex-col">
49 <HelloWorld />
50 </div>
51 </div>
52 </div>
53</template>
54EOF
安裝及配置 Electron
1yarn add --dev electron concurrently wait-on cross-env electron-builder
2
3mkdir -p electron
4
5cat << EOF > electron/main.cjs
6const { app, BrowserWindow } = require('electron')
7const path = require('path')
8
9const NODE_ENV = process.env.NODE_ENV
10
11function createWindow() {
12 const mainWindow = new BrowserWindow({
13 width: 800,
14 height: 600,
15 show: false,
16 autoHideMenuBar: true,
17 webPreferences: {
18 preload: path.join(__dirname, 'preload.cjs')
19 }
20 })
21
22 mainWindow.once('ready-to-show', () => {
23 mainWindow.show()
24 })
25
26 mainWindow.loadURL(
27 NODE_ENV === 'development'
28 ? 'http://localhost:5173'
29 : \`file://\${path.join(__dirname, '../dist/index.html')}\`
30 );
31
32 if (NODE_ENV === "development") {
33 mainWindow.webContents.openDevTools()
34 }
35}
36
37app.whenReady().then(() => {
38 createWindow()
39
40 app.on('activate', function () {
41 if (BrowserWindow.getAllWindows().length === 0) createWindow()
42 })
43})
44
45app.on('window-all-closed', function () {
46 if (process.platform !== 'darwin') app.quit()
47})
48EOF
49
50cat << EOF > electron/preload.cjs
51window.addEventListener('DOMContentLoaded', () => {
52 const replaceText = (selector, text) => {
53 const element = document.getElementById(selector)
54 if (element) element.innerText = text
55 }
56
57 for (const dependency of ['chrome', 'node', 'electron']) {
58 replaceText(\`\${dependency}-version\`, process.versions[dependency])
59 }
60})
61EOF
62
63sed -i '/export default/a \ \ \ \ base: "./",' vite.config.cjs
64
65sed -i '/version/a "main": "electron/main.cjs",' package.json
66
67sed -i '/"build/a "electron": "wait-on tcp:5173 && cross-env NODE_ENV=development electron .",' package.json
68sed -i '/"build":/a "electron:serve": "concurrently -k \\\"yarn dev\\\" \\\"yarn electron\\\"\",' package.json
69sed -i '/"build":/a "electron:build": "vite build && electron-builder",' package.json
70
71sed -i '$d' package.json && sed -i '$d' package.json && cat << EOF >> package.json
72 },
73 "build": {
74 "appId": "com.my-website.my-app",
75 "productName": "MyApp",
76 "copyright": "Copyright © 2022 Yuan",
77 "mac": {
78 "category": "public.app-category.utilities"
79 },
80 "nsis": {
81 "oneClick": false,
82 "allowToChangeInstallationDirectory": true
83 },
84 "files": [
85 "dist/**/*",
86 "electron/**/*"
87 ],
88 "directories": {
89 "buildResources": "assets",
90 "output": "dist_electron"
91 }
92 }
93}
94EOF
95
96yarn electron:serve
修正安全提示中的
1sed -i '/<meta name/a \ \ \ \ \<meta http-equiv="Content-Security-Policy" content="script-src '\''self'\''" /\>' index.html
Add Makefile
為了不用每次編譯時要下什麼指令,所以寫個 Makefile 省心 ~
1TAB="$(printf '\t')"
2
3cat << EOF > Makefile
4all: run
5
6build:
7${TAB}yarn electron:build
8
9.PHONY: web
10web:
11${TAB}yarn dev
12
13.PHONY: run
14run:
15${TAB}yarn electron:serve
16
17.PHONY: clean
18clean:
19${TAB}\$(RM) -r dist dist_electron
20EOF
額外記下來的
順手記下這次有用到的一些東西。
使用 Axios
1yarn add axios
2
3mkdir -p src/composables
4
5echo << EOF > src/composables/useApi.js
6import axios from 'axios';
7
8const instance = axios.create({
9 baseURL: 'http://localhost:8088/',
10 timeout: 1000,
11 headers: {
12 "Content-Type": "application/json",
13 }
14});
15
16const useAxios = ({url, method, arg}) => {
17 return instance[method](url, arg)
18}
19
20export default useAxios;
21EOF
Electron 建立視窗後在背景做點事
1app.on('ready', async () => {
2 const { execFile } = require('child_process')
3
4 execFile('./resources/server/CCSNoteServer.exe')
5
6 createWindow()
7})
1const { ipcRenderer } = require('electron')
小結
筆都在這一篇快要完成的時候更新了 vite 的版本,從 vite 2.99 更新到 3.0.0。 接下來世界就變了 🤣

migrate to ESM
vite 預設使用的 Port 從 3000 改到了 5173。

dev port
修正後基本上只要把上面的指令直接貼在終端機中就可以了。
最後附上執行結果!
1make

screenshot