Vue 3 + Vite + TypeScript + ELECTRON (My Full Setup)

In this post, I will show the full setup I am using when building an electron app. I will do it in step by step that way its a lot easier to follow the setup. I will paste the codes without explanation but I encourage you to take a good understanding on why I put things.

#vue3
#vite
#typescript
#electron
Mar. 08, 2023. 2:19 PM
Ads

I did create a simple post about making vue 3 + vite + electron. But it does not support hot reload if their is a change in electron files you have to close and rerun everything.

In this post, I will show the full setup I am using when building an electron app. I will do it in step by step that way its a lot easier to follow the setup. I will paste the codes without explanation but I encourage you to take a good understanding on why I put things.

In this project we are going to use yarn.If you don't have yarn you can install it by running.

npm install --global yarn

STEP 1 - Create a project

Lets start by creating a folder and running init to setup our package.json.

mkdir electron-vue # make a new folder `electron-vue`. 
cd electron-vue # redirect to electron-vue folder
yarn init # run and just press enters for every question.

STEP 2 - let us install Dev Dependencies that we need.

Install This packages as Dev Dependencies

yarn add -D concurrently electron electron-builder electron-devtools-installer nodemon typescript wait-on json

Install This packages as dependencies

yarn add electron-updater electron-store electron-log

STEP 3: Setup Our Front End App (Vue3 + vite + script)

yarn create vite

run this and make the project name FrontEndApp and select vue and then select typescript.After Doing that you should have a new Folder called FrontEndApp

After Doing that, cd FrontEndApp and install the dependencies by running yarn install

STEP 4: Setup FrontEndApp

Setup This file FrontEndApp\vite.config.ts

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig(({ mode }) => ({
    build: {
        outDir: './../dist',
        chunkSizeWarningLimit: 1000,
    },
    base: mode == 'development' ? '' : './',
    plugins: [vue()],
    server: {
        port: 3000,
    },
}));

This will configure the ouput folder to ./../dist whenever we run build, and also going to make the server default to 3000 this is important.

Go to This file FrontEndApp\package.json and edit the build script and lets add some options. --emptyOutDir will remove the folder whenever we build the FrontEndApp.

"scripts": {
        "dev": "vite",
        "build": "vue-tsc --noEmit &&  vite build --emptyOutDir",
        "preview": "vite preview"
    },

In your root project. add and edit .gitignore

dist
node_modules

After doing all that you can try running yarn build in /FrontEndApp directory if build command works properly.

Like in this image, it successfully built the FrontEndApp to this dist folder.

I think that the setup in our FrontEndApp.

STEP 5: Setup Electron

Lets Create a Folder Electron where we can put all our Electron Typescript Files.

Create a file tsconfig.json in the root folder with this content.

{
    "compilerOptions": {
        "target": "ESNext",
        "module": "CommonJS",
        "outDir": "./dist",
        "removeComments": true,
        "esModuleInterop": true,
        "forceConsistentCasingInFileNames": true,
        "strict": true,
        "skipLibCheck": true
    },
    "include": ["Electron/**/*.ts"]
}

Let us create this files.\Electron\main.ts

import { app, BrowserWindow, BrowserWindowConstructorOptions, ipcMain, screen } from "electron";
import path from "path";
import { isDev } from "./config";
import { appConfig } from "./ElectronStore/Configuration";
import AppUpdater from "./AutoUpdate";

async function createWindow() {
    const { width, height } = screen.getPrimaryDisplay().workAreaSize;
    const appBounds: any = appConfig.get("setting.appBounds");
    const BrowserWindowOptions: BrowserWindowConstructorOptions = {
        width: 1200,
        minWidth: 900,
        height: 750,
        minHeight: 600,

        webPreferences: {
            preload: __dirname + "/preload.js",
            devTools: isDev,
        },
        show: false,
        alwaysOnTop: true,
        frame: true,
    };

    if (appBounds !== undefined && appBounds !== null) Object.assign(BrowserWindowOptions, appBounds);
    const mainWindow = new BrowserWindow(BrowserWindowOptions);

    // auto updated
    if (!isDev) AppUpdater();

    // and load the index.html of the app.
    // win.loadFile("index.html");
    await mainWindow.loadURL(isDev ? "http://localhost:3000" : `file://${path.join(__dirname, "./index.html")}`);

    if (appBounds !== undefined && appBounds !== null && appBounds.width > width && appBounds.height > height) mainWindow.maximize();
    else mainWindow.show();

    // this will turn off always on top after opening the application
    setTimeout(() => {
        mainWindow.setAlwaysOnTop(false);
    }, 1000);

    // Open the DevTools.
    if (isDev) {
        mainWindow.webContents.openDevTools();
    }


    ipcMain.handle('versions', () => {
        return {
            node: process.versions.chrome,
            chrome: process.versions.chrome,
            electron: process.versions.electron,
            version: app.getVersion(),
            name: app.getName(),
        };
    });
}

// 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(async () => {
    // if dev
    if (isDev) {
        try {
            const { installExt } = await import("./installDevTool");
            await installExt();
        } catch (e) {
            console.log("Can not install extension!");
        }
    }

    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();
    }
});


Electron\installDevTool.ts

import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer';

export async function installExt() {
    await installExtension(VUEJS_DEVTOOLS)
        .then(() => {
            console.log('Added Extension');
        })
        .catch((err) => {
            console.log('Extension Error: ', err);
        });
}

Electron\config.ts

export const isDev = process.env.APP_IS_DEV ? true : false;

Electron\AutoUpdate.ts

import { dialog, app } from 'electron';
import { autoUpdater, UpdateInfo } from 'electron-updater';

export default () => {
    if (app.isPackaged) {
        autoUpdater.autoInstallOnAppQuit = true;
        autoUpdater.checkForUpdates();
        autoUpdater.addListener('update-downloaded', (info: UpdateInfo) => {
            dialog
                .showMessageBox({
                    title: 'Restart Believers Sword?',
                    type: 'question',
                    message: `New version "Believers Sword ${info.version}" has been successfully downloaded.`,
                    buttons: ['Yes', 'Later', 'Yes, Update'],
                    cancelId: 1,
                })
                .then(({ response }) => {
                    if (response == 0 || response == 2) {
                        autoUpdater.quitAndInstall();
                    }
                });
        });
    }
};

Electron\ElectronStore\Configuration.ts

import ElectronStore from 'electron-store';

export const appConfig = new ElectronStore({
    name: 'appConfig',
    defaults: {
        setting: {},
    },
    schema: {
        setting: {
            type: 'object',
        },
    },
});

Electron/preload.ts

import { contextBridge, ipcRenderer } from "electron";

contextBridge.exposeInMainWorld("browserWindow", {
    versions: () => ipcRenderer.invoke("versions"),
});

package.json

{
  "name": "electron-vue",
  "version": "1.0.0",
  "description": "This is a sample",
  "main": "./dist/main.js",
  "license": "MIT",
  "author": "BroJenuel",
  "scripts": {
    "build": "tsc",
    "watch": "nodemon --exec yarn serve:electron",
    "start": "yarn nightly:rename && concurrently -k \"yarn serve:front\" \"yarn watch\"",
    "front:setup": "cd FrontEndApp && yarn",
    "electron:setup": "yarn",
    "setup": "yarn front:setup && yarn electron:setup",
    "serve:front": "cd FrontEndApp && yarn dev",
    "serve:electron": "set APP_IS_NIGHTLY=yes && set APP_IS_DEV=yes && yarn build && wait-on tcp:3000 && electron .",
    "build:front": "cd FrontEndApp && yarn build",
    "electron:build": "yarn build",
    "electron:builder": "electron-builder",
    "app:build": "yarn prod:rename && yarn build:front && yarn electron:build && yarn electron:builder",
    "app:build:nightly": "set APP_IS_NIGHTLY=yes && set APP_IS_DEV=no && yarn nightly:rename && yarn build:front && yarn electron:build && yarn electron:builder",
    "nightly:rename": "json -I -f package.json -e \"this.name='electron-vue'\" && json -I -f package.json -e \"this.build.productName='Electron Vue Nightly'\" && json -I -f package.json -e \"this.build.appId='com.official-electron-vue.app'\"",
    "prod:rename": "json -I -f package.json -e \"this.name='electron-vue'\" && json -I -f package.json -e \"this.build.productName='Electron Vue'\" && json -I -f package.json -e \"this.build.appId='com.official-electron-vue.app'\"",
    "electron-rebuild": "./node_modules/.bin/electron-rebuild",
    "sqlite-rebuild": "cd node_modules/better-sqlite3 && npm run build-release",
    "postinstall": "electron-builder install-app-deps",
    "up:win": "up_using_window.cmd"
  },
  "devDependencies": {
    "concurrently": "^7.6.0",
    "electron": "^22.0.0",
    "electron-builder": "^23.6.0",
    "electron-devtools-installer": "^3.2.0",
    "json": "^11.0.0",
    "nodemon": "^2.0.20",
    "typescript": "^4.9.4",
    "wait-on": "^7.0.1"
  },
  "dependencies": {
    "electron-log": "^4.4.8",
    "electron-store": "^8.1.0",
    "electron-updater": "^5.3.0"
  },
  "build": {
    "appId": "com.official-electron-vue.app",
    "productName": "Electron Vue Nightly",
    "copyright": "Copyright © 2022 ${author}",
    "publish": [
      {
        "provider": "github",
        "owner": "Bible-Projects"
      }
    ],
    "nsis": {
      "oneClick": true,
      "allowToChangeInstallationDirectory": false,
      "deleteAppDataOnUninstall": true
    },
    "files": [
      "dist/**/*"
    ],
    "extraResources": [
      "defaults/**/*"
    ],
    "directories": {
      "buildResources": "assets",
      "output": "dist_electron"
    }
  }
}

Next Create a Nodemon file to configure the files to watch nodemon.json

{
    "watch": ["Electron/*"],
    "ext": "js,css,ts,json"
}

STEP 6: Start Up Development Mode

You can start development mode by running. For more scripts you can review the script property in the package.json

yarn start

Run yarn app:build for production or you can run yarn app:build:nightly for nightly version.

when running build commands make sure to add github repository property on your package.json, and add your repository link as the value.


If you enjoy this article and would like to show your support, you can easily do so by buying me a coffee. Your contribution is greatly appreciated!

Jenuel Ganawed Buy me Coffee