├── .gitignore
├── LICENSE
├── README.md
├── electron-builder.json
├── package-lock.json
├── package.json
├── scripts
├── build.js
├── dev-server.js
└── private
│ └── tsc.js
├── src
├── main
│ ├── main.ts
│ ├── preload.ts
│ ├── static
│ │ └── .gitkeep
│ └── tsconfig.json
└── renderer
│ ├── App.vue
│ ├── assets
│ ├── vite.svg
│ └── vue.svg
│ ├── components
│ └── HelloWorld.vue
│ ├── index.html
│ ├── main.ts
│ ├── style.css
│ ├── tsconfig.json
│ └── typings
│ ├── electron.d.ts
│ └── shims-vue.d.ts
└── vite.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | build
4 |
5 | .vscode
6 | .idea
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Deluze
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Electron Vue Template
4 |
5 |
6 |
7 | A simple starter template for a **Vue3** + **Electron** TypeScript based application, including **ViteJS** and **Electron Builder**.
8 |
9 |
10 | ## About
11 |
12 | This template utilizes [ViteJS](https://vitejs.dev) for building and serving your (Vue powered) front-end process, it provides Hot Reloads (HMR) to make development fast and easy ⚡
13 |
14 | Building the Electron (main) process is done with [Electron Builder](https://www.electron.build/), which makes your application easily distributable and supports cross-platform compilation 😎
15 |
16 | ## Getting started
17 |
18 | Click the green **Use this template** button on top of the repository, and clone your own newly created repository.
19 |
20 | **Or..**
21 |
22 | Clone this repository: `git clone git@github.com:Deluze/electron-vue-template.git`
23 |
24 |
25 | ### Install dependencies ⏬
26 |
27 | ```bash
28 | npm install
29 | ```
30 |
31 | ### Start developing ⚒️
32 |
33 | ```bash
34 | npm run dev
35 | ```
36 |
37 | ## Additional Commands
38 |
39 | ```bash
40 | npm run dev # starts application with hot reload
41 | npm run build # builds application, distributable files can be found in "dist" folder
42 |
43 | # OR
44 |
45 | npm run build:win # uses windows as build target
46 | npm run build:mac # uses mac as build target
47 | npm run build:linux # uses linux as build target
48 | ```
49 |
50 | Optional configuration options can be found in the [Electron Builder CLI docs](https://www.electron.build/cli.html).
51 | ## Project Structure
52 |
53 | ```bash
54 | - scripts/ # all the scripts used to build or serve your application, change as you like.
55 | - src/
56 | - main/ # Main thread (Electron application source)
57 | - renderer/ # Renderer thread (VueJS application source)
58 | ```
59 |
60 | ## Using static files
61 |
62 | If you have any files that you want to copy over to the app directory after installation, you will need to add those files in your `src/main/static` directory.
63 |
64 | Files in said directory are only accessible to the `main` process, similar to `src/renderer/assets` only being accessible to the `renderer` process. Besides that, the concept is the same as to what you're used to in your other front-end projects.
65 |
66 | #### Referencing static files from your main process
67 |
68 | ```ts
69 | /* Assumes src/main/static/myFile.txt exists */
70 |
71 | import {app} from 'electron';
72 | import {join} from 'path';
73 | import {readFileSync} from 'fs';
74 |
75 | const path = join(app.getAppPath(), 'static', 'myFile.txt');
76 | const buffer = readFileSync(path);
77 | ```
78 |
--------------------------------------------------------------------------------
/electron-builder.json:
--------------------------------------------------------------------------------
1 | {
2 | "appId": "com.electron.app",
3 | "directories": {
4 | "output": "dist"
5 | },
6 |
7 | "nsis": {
8 | "oneClick": false,
9 | "perMachine": false,
10 | "allowToChangeInstallationDirectory": true,
11 | "shortcutName": "Electron App"
12 | },
13 | "win": {
14 | "target": "nsis"
15 | },
16 | "linux": {
17 | "target": ["snap"]
18 | },
19 | "files": [
20 | {
21 | "from": "build/main",
22 | "to": "main",
23 | "filter": ["**/*"]
24 | },
25 | {
26 | "from": "build/renderer",
27 | "to": "renderer",
28 | "filter": ["**/*"]
29 | },
30 | {
31 | "from": "src/main/static",
32 | "to": "static",
33 | "filter": ["**/*"]
34 | },
35 | "!build",
36 | "!dist",
37 | "!scripts"
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "electron-vue-template",
3 | "version": "0.1.0",
4 | "description": "A minimal Electron + Vue application",
5 | "main": "main/main.js",
6 | "scripts": {
7 | "dev": "node scripts/dev-server.js",
8 | "build": "node scripts/build.js && electron-builder",
9 | "build:win": "node scripts/build.js && electron-builder --win",
10 | "build:mac": "node scripts/build.js && electron-builder --mac",
11 | "build:linux": "node scripts/build.js && electron-builder --linux"
12 | },
13 | "repository": "https://github.com/deluze/electron-vue-template",
14 | "author": {
15 | "name": "Deluze",
16 | "url": "https://github.com/Deluze"
17 | },
18 | "devDependencies": {
19 | "@vitejs/plugin-vue": "^4.4.1",
20 | "chalk": "^4.1.2",
21 | "chokidar": "^3.5.3",
22 | "electron": "^32.1.2",
23 | "electron-builder": "^25.1.6",
24 | "typescript": "^5.2.2",
25 | "vite": "^4.5.0"
26 | },
27 | "dependencies": {
28 | "vue": "^3.3.8"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/scripts/build.js:
--------------------------------------------------------------------------------
1 | const Path = require('path');
2 | const Chalk = require('chalk');
3 | const FileSystem = require('fs');
4 | const Vite = require('vite');
5 | const compileTs = require('./private/tsc');
6 |
7 | function buildRenderer() {
8 | return Vite.build({
9 | configFile: Path.join(__dirname, '..', 'vite.config.js'),
10 | base: './',
11 | mode: 'production'
12 | });
13 | }
14 |
15 | function buildMain() {
16 | const mainPath = Path.join(__dirname, '..', 'src', 'main');
17 | return compileTs(mainPath);
18 | }
19 |
20 | FileSystem.rmSync(Path.join(__dirname, '..', 'build'), {
21 | recursive: true,
22 | force: true,
23 | })
24 |
25 | console.log(Chalk.blueBright('Transpiling renderer & main...'));
26 |
27 | Promise.allSettled([
28 | buildRenderer(),
29 | buildMain(),
30 | ]).then(() => {
31 | console.log(Chalk.greenBright('Renderer & main successfully transpiled! (ready to be built with electron-builder)'));
32 | });
33 |
--------------------------------------------------------------------------------
/scripts/dev-server.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = 'development';
2 |
3 | const Vite = require('vite');
4 | const ChildProcess = require('child_process');
5 | const Path = require('path');
6 | const Chalk = require('chalk');
7 | const Chokidar = require('chokidar');
8 | const Electron = require('electron');
9 | const compileTs = require('./private/tsc');
10 | const FileSystem = require('fs');
11 | const { EOL } = require('os');
12 |
13 | let viteServer = null;
14 | let electronProcess = null;
15 | let electronProcessLocker = false;
16 | let rendererPort = 0;
17 |
18 | async function startRenderer() {
19 | viteServer = await Vite.createServer({
20 | configFile: Path.join(__dirname, '..', 'vite.config.js'),
21 | mode: 'development',
22 | });
23 |
24 | return viteServer.listen();
25 | }
26 |
27 | async function startElectron() {
28 | if (electronProcess) { // single instance lock
29 | return;
30 | }
31 |
32 | try {
33 | await compileTs(Path.join(__dirname, '..', 'src', 'main'));
34 | } catch {
35 | console.log(Chalk.redBright('Could not start Electron because of the above typescript error(s).'));
36 | electronProcessLocker = false;
37 | return;
38 | }
39 |
40 | const args = [
41 | Path.join(__dirname, '..', 'build', 'main', 'main.js'),
42 | rendererPort,
43 | ];
44 | electronProcess = ChildProcess.spawn(Electron, args);
45 | electronProcessLocker = false;
46 |
47 | electronProcess.stdout.on('data', data => {
48 | if (data == EOL) {
49 | return;
50 | }
51 |
52 | process.stdout.write(Chalk.blueBright(`[electron] `) + Chalk.white(data.toString()))
53 | });
54 |
55 | electronProcess.stderr.on('data', data =>
56 | process.stderr.write(Chalk.blueBright(`[electron] `) + Chalk.white(data.toString()))
57 | );
58 |
59 | electronProcess.on('exit', () => stop());
60 | }
61 |
62 | function restartElectron() {
63 | if (electronProcess) {
64 | electronProcess.removeAllListeners('exit');
65 | electronProcess.kill();
66 | electronProcess = null;
67 | }
68 |
69 | if (!electronProcessLocker) {
70 | electronProcessLocker = true;
71 | startElectron();
72 | }
73 | }
74 |
75 | function copyStaticFiles() {
76 | copy('static');
77 | }
78 |
79 | /*
80 | The working dir of Electron is build/main instead of src/main because of TS.
81 | tsc does not copy static files, so copy them over manually for dev server.
82 | */
83 | function copy(path) {
84 | FileSystem.cpSync(
85 | Path.join(__dirname, '..', 'src', 'main', path),
86 | Path.join(__dirname, '..', 'build', 'main', path),
87 | { recursive: true }
88 | );
89 | }
90 |
91 | function stop() {
92 | viteServer.close();
93 | process.exit();
94 | }
95 |
96 | async function start() {
97 | console.log(`${Chalk.greenBright('=======================================')}`);
98 | console.log(`${Chalk.greenBright('Starting Electron + Vite Dev Server...')}`);
99 | console.log(`${Chalk.greenBright('=======================================')}`);
100 |
101 | const devServer = await startRenderer();
102 | rendererPort = devServer.config.server.port;
103 |
104 | copyStaticFiles();
105 | startElectron();
106 |
107 | const path = Path.join(__dirname, '..', 'src', 'main');
108 | Chokidar.watch(path, {
109 | cwd: path,
110 | }).on('change', (path) => {
111 | console.log(Chalk.blueBright(`[electron] `) + `Change in ${path}. reloading... 🚀`);
112 |
113 | if (path.startsWith(Path.join('static', '/'))) {
114 | copy(path);
115 | }
116 |
117 | restartElectron();
118 | });
119 | }
120 |
121 | start();
122 |
--------------------------------------------------------------------------------
/scripts/private/tsc.js:
--------------------------------------------------------------------------------
1 | const ChildProcess = require('child_process');
2 | const Chalk = require('chalk');
3 |
4 | function compile(directory) {
5 | return new Promise((resolve, reject) => {
6 | const tscProcess = ChildProcess.exec('tsc', {
7 | cwd: directory,
8 | });
9 |
10 | tscProcess.stdout.on('data', data =>
11 | process.stdout.write(Chalk.yellowBright(`[tsc] `) + Chalk.white(data.toString()))
12 | );
13 |
14 | tscProcess.on('exit', exitCode => {
15 | if (exitCode > 0) {
16 | reject(exitCode);
17 | } else {
18 | resolve();
19 | }
20 | });
21 | });
22 | }
23 |
24 | module.exports = compile;
25 |
--------------------------------------------------------------------------------
/src/main/main.ts:
--------------------------------------------------------------------------------
1 | import {app, BrowserWindow, ipcMain, session} from 'electron';
2 | import {join} from 'path';
3 |
4 | function createWindow () {
5 | const mainWindow = new BrowserWindow({
6 | width: 800,
7 | height: 600,
8 | webPreferences: {
9 | preload: join(__dirname, 'preload.js'),
10 | nodeIntegration: false,
11 | contextIsolation: true,
12 | }
13 | });
14 |
15 | if (process.env.NODE_ENV === 'development') {
16 | const rendererPort = process.argv[2];
17 | mainWindow.loadURL(`http://localhost:${rendererPort}`);
18 | }
19 | else {
20 | mainWindow.loadFile(join(app.getAppPath(), 'renderer', 'index.html'));
21 | }
22 | }
23 |
24 | app.whenReady().then(() => {
25 | createWindow();
26 |
27 | session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
28 | callback({
29 | responseHeaders: {
30 | ...details.responseHeaders,
31 | 'Content-Security-Policy': ['script-src \'self\'']
32 | }
33 | })
34 | })
35 |
36 | app.on('activate', function () {
37 | // On macOS it's common to re-create a window in the app when the
38 | // dock icon is clicked and there are no other windows open.
39 | if (BrowserWindow.getAllWindows().length === 0) {
40 | createWindow();
41 | }
42 | });
43 | });
44 |
45 | app.on('window-all-closed', function () {
46 | if (process.platform !== 'darwin') app.quit()
47 | });
48 |
49 | ipcMain.on('message', (event, message) => {
50 | console.log(message);
51 | })
--------------------------------------------------------------------------------
/src/main/preload.ts:
--------------------------------------------------------------------------------
1 | import {contextBridge, ipcRenderer} from 'electron';
2 |
3 | contextBridge.exposeInMainWorld('electronAPI', {
4 | sendMessage: (message: string) => ipcRenderer.send('message', message)
5 | })
6 |
--------------------------------------------------------------------------------
/src/main/static/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Deluze/electron-vue-template/fceca5ec8ce9ac5a2922dddc1b732b7ba043a63a/src/main/static/.gitkeep
--------------------------------------------------------------------------------
/src/main/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2015",
4 | "module": "commonjs",
5 | "strict": true,
6 | "esModuleInterop": true,
7 | "skipLibCheck": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "outDir": "../../build/main",
10 | "allowJs": true,
11 | "noImplicitAny": false,
12 | },
13 | "exclude": ["static"]
14 | }
15 |
--------------------------------------------------------------------------------
/src/renderer/App.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |