commit 95c52d8a111aea66c36ea0478ab852b4d4357de6 Author: Jeffrey Li Date: Thu Oct 6 15:52:01 2022 -0700 Initial commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..4a7ea3036 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..3c0d0bbcc --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +release +.vscode/.debug.env +package-lock.json +pnpm-lock.yaml +yarn.lock +dist-electron diff --git a/.vscode/.debug.script.mjs b/.vscode/.debug.script.mjs new file mode 100644 index 000000000..ade3e7944 --- /dev/null +++ b/.vscode/.debug.script.mjs @@ -0,0 +1,24 @@ +import fs from 'fs' +import path from 'path' +import { fileURLToPath } from 'url' +import { createRequire } from 'module' +import { spawn } from 'child_process' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const require = createRequire(import.meta.url) +const pkg = require('../package.json') + +// write .debug.env +const envContent = Object.entries(pkg.debug.env).map(([key, val]) => `${key}=${val}`) +fs.writeFileSync(path.join(__dirname, '.debug.env'), envContent.join('\n')) + +// bootstrap +spawn( + // TODO: terminate `npm run dev` when Debug exits. + process.platform === 'win32' ? 'npm.cmd' : 'npm', + ['run', 'dev'], + { + stdio: 'inherit', + env: Object.assign(process.env, { VSCODE_DEBUG: 'true' }), + }, +) diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..47466d460 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "editorconfig.editorconfig", + "mrmlnc.vscode-json5", + "rbbit.typescript-hero", + "syler.sass-indented", + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..c36a0e254 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,47 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "compounds": [ + { + "name": "Debug App", + "preLaunchTask": "start .debug.script.mjs", + "configurations": [ + "Debug Main Process", + "Debug Renderer Process" + ], + "presentation": { + "hidden": false, + "group": "", + "order": 1 + }, + "stopAll": true + } + ], + "configurations": [ + { + "name": "Debug Main Process", + "type": "pwa-node", + "request": "launch", + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron", + "windows": { + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd" + }, + "runtimeArgs": [ + "--no-sandbox", + "--remote-debugging-port=9229", + "." + ], + "envFile": "${workspaceFolder}/.vscode/.debug.env", + "console": "integratedTerminal" + }, + { + "name": "Debug Renderer Process", + "port": 9229, + "request": "attach", + "type": "pwa-chrome", + "timeout": 60000 + }, + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..ef1386a09 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,34 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "start .debug.script.mjs", + "type": "shell", + "command": "node .vscode/.debug.script.mjs", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "fileLocation": "relative", + "pattern": { + // TODO: correct "regexp" + "regexp": "^([a-zA-Z]\\:\/?([\\w\\-]\/?)+\\.\\w+):(\\d+):(\\d+): (ERROR|WARNING)\\: (.*)$", + "file": 1, + "line": 3, + "column": 4, + "code": 5, + "message": 6 + }, + "background": { + "activeOnStart": true, + "endsPattern": "^.*[startup] Electron App.*$", + } + } + } + ] +} + +// https://code.visualstudio.com/docs/editor/tasks#_operating-system-specific-properties +// https://code.visualstudio.com/docs/editor/tasks#_background-watching-tasks +// https://code.visualstudio.com/docs/editor/tasks#_can-a-background-task-be-used-as-a-prelaunchtask-in-launchjson diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..8fd517583 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 草鞋没号 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 000000000..d895496a0 --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +# electron-vite-react + +[![awesome-vite](https://awesome.re/mentioned-badge.svg)](https://github.com/vitejs/awesome-vite) +![GitHub stars](https://img.shields.io/github/stars/caoxiemeihao/vite-react-electron?color=fa6470) +![GitHub issues](https://img.shields.io/github/issues/caoxiemeihao/vite-react-electron?color=d8b22d) +![GitHub license](https://img.shields.io/github/license/caoxiemeihao/vite-react-electron) +[![Required Node.JS >= 14.18.0 || >=16.0.0](https://img.shields.io/static/v1?label=node&message=14.18.0%20||%20%3E=16.0.0&logo=node.js&color=3f893e)](https://nodejs.org/about/releases) + +English | [简体中文](README.zh-CN.md) + +## 👀 Overview + +📦 Ready out of the box +🎯 Based on the official [template-react-ts](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts), project structure will be familiar to you +🌱 Easily extendable and customizable +💪 Supports Node.js API in the renderer process +🔩 Supports C/C++ native addons +🐞 Debugger configuration included +🖥 Easy to implement multiple windows + +## 🛫 Quick start + +```sh +npm create electron-vite +``` + +![electron-vite-react.gif](https://github.com/electron-vite/electron-vite-react/blob/main/public/electron-vite-react.gif?raw=true) + +## 🐞 Debug + +![electron-vite-react-debug.gif](https://github.com/electron-vite/electron-vite-react/blob/main/public/electron-vite-react-debug.gif?raw=true) + +## 📂 Directory structure + +Familiar React application structure, just with `electron` folder on the top :wink: +*Files in this folder will be separated from your React application and built into `dist/electron`* + +```tree +├── electron Electron-related code +│ ├── main Main-process source code +│ ├── preload Preload-scripts source code +│ └── resources Resources for the production build +│ ├── icon.icns Icon for the application on macOS +│ ├── icon.ico Icon for the application +│ ├── installerIcon.ico Icon for the application installer +│ ├── uninstallerIcon.ico Icon for the application uninstaller +| └── iconset +| └── 256x256.png Icon for the application on Linux +│ +├── release Generated after production build, contains executables +│ └── {version} +│ ├── {os}-unpacked Contains unpacked application executable +│ └── Setup.{ext} Installer for the application +│ +├── public Static assets +└── src Renderer source code, your React application +``` + +## 🚨 Be aware + +This template integrates Node.js API to the renderer process by default. If you want to follow **Electron Security Concerns** you might want to disable this feature. You will have to expose needed API by yourself. + +To get started, remove the option as shown below. This will [modify the Vite configuration and disable this feature](https://github.com/electron-vite/vite-plugin-electron/tree/main/packages/electron-renderer#config-presets-opinionated). + +```diff +# vite.config.ts + +electron({ +- renderer: {} +}) +``` + +## ❔ FAQ + +- [dependencies vs devDependencies](https://github.com/electron-vite/vite-plugin-electron/tree/main/packages/electron-renderer#dependencies-vs-devdependencies) +- [Using C/C++ native addons in renderer](https://github.com/electron-vite/vite-plugin-electron/tree/main/packages/electron-renderer#load-nodejs-cc-native-modules) +- [Node.js ESM packages](https://github.com/electron-vite/vite-plugin-electron/tree/main/packages/electron-renderer#nodejs-esm-packages) (e.g. `execa` `node-fetch`) diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 000000000..d76f46b4c --- /dev/null +++ b/README.zh-CN.md @@ -0,0 +1,72 @@ +# vite-react-electron + +[![awesome-vite](https://awesome.re/mentioned-badge.svg)](https://github.com/vitejs/awesome-vite) +![GitHub stars](https://img.shields.io/github/stars/caoxiemeihao/vite-react-electron?color=fa6470) +![GitHub issues](https://img.shields.io/github/issues/caoxiemeihao/vite-react-electron?color=d8b22d) +![GitHub license](https://img.shields.io/github/license/caoxiemeihao/vite-react-electron) +[![Required Node.JS >= 14.18.0 || >=16.0.0](https://img.shields.io/static/v1?label=node&message=14.18.0%20||%20%3E=16.0.0&logo=node.js&color=3f893e)](https://nodejs.org/about/releases) + +[English](README.md) | 简体中文 + +## 概述 + +📦 开箱即用 +🎯 基于官方的 [template-react-ts](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts), 低侵入性 +🌱 结构清晰,可塑性强 +💪 支持在渲染进程中使用 Electron、Node.js API +🔩 支持 C/C++ 模块 +🖥 很容易实现多窗口 + +## 快速开始 + +```sh +npm create electron-vite +``` + +![electron-vite-react.gif](https://github.com/electron-vite/electron-vite-react/blob/main/public/electron-vite-react.gif?raw=true) + +## 调试 + +![electron-vite-react-debug.gif](https://github.com/electron-vite/electron-vite-react/blob/main/public/electron-vite-react-debug.gif?raw=true) + +## 目录 + +*🚨 默认情况下, `electron` 文件夹下的文件将会被构建到 `dist/electron`* + +```tree +├── electron Electron 源码文件夹 +│ ├── main Main-process 源码 +│ ├── preload Preload-scripts 源码 +│ └── resources 应用打包的资源文件夹 +│ ├── icon.icns 应用图标(macOS) +│ ├── icon.ico 应用图标 +│ ├── installerIcon.ico 安装图标 +│ └── uninstallerIcon.ico 卸载图标 +│ +├── release 构建后生成程序目录 +│ └── {version} +│ ├── {os}-unpacked 未打包的程序(绿色运行版) +│ └── Setup.{ext} 应用安装文件 +│ +├── public 同 Vite 模板的 public +└── src 渲染进程源码、React代码 +``` + + +## 🚨 这需要留神 + +默认情况下,该模板在渲染进程中集成了 Node.js,如果你不需要它,你只需要删除下面的选项. [因为它会修改 Vite 默认的配置](https://github.com/electron-vite/vite-plugin-electron/tree/main/packages/electron-renderer#config-presets-opinionated). + +```diff +# vite.config.ts + +electron({ +- renderer: {} +}) +``` + +## FAQ + +- [dependencies vs devDependencies](https://github.com/electron-vite/vite-plugin-electron/tree/main/packages/electron-renderer#dependencies-vs-devdependencies) +- [Using C/C++ native addons in Electron-Renderer](https://github.com/electron-vite/vite-plugin-electron/tree/main/packages/electron-renderer#load-nodejs-cc-native-modules) +- [Node.js ESM packages](https://github.com/electron-vite/vite-plugin-electron/tree/main/packages/electron-renderer#nodejs-esm-packages) (e.g. `execa` `node-fetch`) diff --git a/electron-builder.json b/electron-builder.json new file mode 100644 index 000000000..74694e49c --- /dev/null +++ b/electron-builder.json @@ -0,0 +1,59 @@ +{ + "appId": "TEST", + "productName": "TEST", + "copyright": "Copyright © 2022 ${author}", + "directories": { + "app": "release/app", + "output": "release/build", + "buildResources": "electron/resources" + }, + "extends": null, + "asar": true, + "asarUnpack": ["**\\*.{node,dll}", "prisma"], + "files": [ + "dist", + "node_modules", + "package.json", + "prisma/**/*", + "resources/**/*", + "!**/node_modules/@prisma/engines/introspection-engine*", + "!**/node_modules/@prisma/engines/migration-engine*", + "!**/node_modules/@prisma/engines/prisma-fmt*", + "!**/node_modules/@prisma/engines/query_engine-*", + "!**/node_modules/@prisma/engines/libquery_engine*", + "!**/node_modules/prisma/query_engine*", + "!**/node_modules/prisma/libquery_engine*", + "!**/node_modules/prisma/**/*.mjs" + ], + "win": { + "target": [ + { + "target": "nsis", + "arch": ["x64"] + } + ], + "artifactName": "${productName}-Windows-${version}-Setup.${ext}" + }, + "nsis": { + "oneClick": false, + "perMachine": false, + "allowToChangeInstallationDirectory": true, + "deleteAppDataOnUninstall": false + }, + "mac": { + "target": ["dmg"], + "artifactName": "${productName}-Mac-${version}-Installer.${ext}" + }, + "linux": { + "icon": "electron/resources/iconset", + "target": ["AppImage", "deb"], + "artifactName": "${productName}-Linux-${version}.${ext}" + }, + "extraResources": [ + "./assets/**", + "prisma/**/*", + "node_modules/@prisma/engines/migration-engine*", + "node_modules/@prisma/engines/query*", + "node_modules/@prisma/engines/libquery*" + ] +} diff --git a/electron/electron-env.d.ts b/electron/electron-env.d.ts new file mode 100644 index 000000000..3b3399809 --- /dev/null +++ b/electron/electron-env.d.ts @@ -0,0 +1,11 @@ +/// + +declare namespace NodeJS { + interface ProcessEnv { + VSCODE_DEBUG?: 'true' + DIST_ELECTRON: string + DIST: string + /** /dist/ or /public/ */ + PUBLIC: string + } +} diff --git a/electron/main/index.ts b/electron/main/index.ts new file mode 100644 index 000000000..b3bdcf398 --- /dev/null +++ b/electron/main/index.ts @@ -0,0 +1,106 @@ +// The built directory structure +// +// ├─┬ dist-electron +// │ ├─┬ main +// │ │ └── index.js > Electron-Main +// │ └─┬ preload +// │ └── index.js > Preload-Scripts +// ├─┬ dist +// │ └── index.html > Electron-Renderer +// +process.env.DIST_ELECTRON = join(__dirname, ".."); +process.env.DIST = join(process.env.DIST_ELECTRON, "../dist"); +process.env.PUBLIC = app.isPackaged + ? process.env.DIST + : join(process.env.DIST_ELECTRON, "../public"); + +import { app, BrowserWindow, shell, ipcMain } from "electron"; +import { release } from "os"; +import { join } from "path"; + +// Disable GPU Acceleration for Windows 7 +if (release().startsWith("6.1")) app.disableHardwareAcceleration(); + +// Set application name for Windows 10+ notifications +if (process.platform === "win32") app.setAppUserModelId(app.getName()); + +if (!app.requestSingleInstanceLock()) { + app.quit(); + process.exit(0); +} + +let win: BrowserWindow | null = null; +// Here, you can also use other preload +const preload = join(__dirname, "../preload/index.js"); +const url = process.env.VITE_DEV_SERVER_URL; +const indexHtml = join(process.env.DIST, "index.html"); + +async function createWindow() { + win = new BrowserWindow({ + title: "Main window", + icon: join(process.env.PUBLIC, "favicon.svg"), + webPreferences: { + preload, + nodeIntegration: false, + contextIsolation: true, + }, + }); + + if (app.isPackaged) { + win.loadFile(indexHtml); + } else { + win.loadURL(url); + // win.webContents.openDevTools() + } + + // Test actively push message to the Electron-Renderer + win.webContents.on("did-finish-load", () => { + win?.webContents.send("main-process-message", new Date().toLocaleString()); + }); + + // Make all links open with the browser, not with the application + win.webContents.setWindowOpenHandler(({ url }) => { + if (url.startsWith("https:")) shell.openExternal(url); + return { action: "deny" }; + }); +} + +app.whenReady().then(createWindow); + +app.on("window-all-closed", () => { + win = null; + if (process.platform !== "darwin") app.quit(); +}); + +app.on("second-instance", () => { + if (win) { + // Focus on the main window if the user tried to open another + if (win.isMinimized()) win.restore(); + win.focus(); + } +}); + +app.on("activate", () => { + const allWindows = BrowserWindow.getAllWindows(); + if (allWindows.length) { + allWindows[0].focus(); + } else { + createWindow(); + } +}); + +// new window example arg: new windows url +ipcMain.handle("open-win", (event, arg) => { + const childWindow = new BrowserWindow({ + webPreferences: { + preload, + }, + }); + + if (app.isPackaged) { + childWindow.loadFile(indexHtml, { hash: arg }); + } else { + childWindow.loadURL(`${url}/#${arg}`); + // childWindow.webContents.openDevTools({ mode: "undocked", activate: true }) + } +}); diff --git a/electron/preload/index.ts b/electron/preload/index.ts new file mode 100644 index 000000000..206fea06b --- /dev/null +++ b/electron/preload/index.ts @@ -0,0 +1,99 @@ +import { contextBridge } from "electron" + +function domReady(condition: DocumentReadyState[] = ['complete', 'interactive']) { + return new Promise(resolve => { + if (condition.includes(document.readyState)) { + resolve(true) + } else { + document.addEventListener('readystatechange', () => { + if (condition.includes(document.readyState)) { + resolve(true) + } + }) + } + }) +} + +const safeDOM = { + append(parent: HTMLElement, child: HTMLElement) { + if (!Array.from(parent.children).find(e => e === child)) { + return parent.appendChild(child) + } + }, + remove(parent: HTMLElement, child: HTMLElement) { + if (Array.from(parent.children).find(e => e === child)) { + return parent.removeChild(child) + } + }, +} + +/** + * https://tobiasahlin.com/spinkit + * https://connoratherton.com/loaders + * https://projects.lukehaas.me/css-loaders + * https://matejkustec.github.io/SpinThatShit + */ +function useLoading() { + const className = `loaders-css__square-spin` + const styleContent = ` +@keyframes square-spin { + 25% { transform: perspective(100px) rotateX(180deg) rotateY(0); } + 50% { transform: perspective(100px) rotateX(180deg) rotateY(180deg); } + 75% { transform: perspective(100px) rotateX(0) rotateY(180deg); } + 100% { transform: perspective(100px) rotateX(0) rotateY(0); } +} +.${className} > div { + animation-fill-mode: both; + width: 50px; + height: 50px; + background: #fff; + animation: square-spin 3s 0s cubic-bezier(0.09, 0.57, 0.49, 0.9) infinite; +} +.app-loading-wrap { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + display: flex; + align-items: center; + justify-content: center; + background: #282c34; + z-index: 9; +} + ` + const oStyle = document.createElement('style') + const oDiv = document.createElement('div') + + oStyle.id = 'app-loading-style' + oStyle.innerHTML = styleContent + oDiv.className = 'app-loading-wrap' + oDiv.innerHTML = `
` + + return { + appendLoading() { + safeDOM.append(document.head, oStyle) + safeDOM.append(document.body, oDiv) + }, + removeLoading() { + safeDOM.remove(document.head, oStyle) + safeDOM.remove(document.body, oDiv) + }, + } +} + +// ---------------------------------------------------------------------- + +const { appendLoading, removeLoading } = useLoading() +domReady().then(appendLoading) + +window.onmessage = ev => { + ev.data.payload === 'removeLoading' && removeLoading() +} + +setTimeout(removeLoading, 4999) + + +contextBridge.exposeInMainWorld('electron', { + doThing: () => console.log('hello'), +}); diff --git a/electron/resources/icon.icns b/electron/resources/icon.icns new file mode 100644 index 000000000..9a9c785f4 Binary files /dev/null and b/electron/resources/icon.icns differ diff --git a/electron/resources/icon.ico b/electron/resources/icon.ico new file mode 100644 index 000000000..bf153e107 Binary files /dev/null and b/electron/resources/icon.ico differ diff --git a/electron/resources/iconset/256x256.png b/electron/resources/iconset/256x256.png new file mode 100644 index 000000000..4526115c6 Binary files /dev/null and b/electron/resources/iconset/256x256.png differ diff --git a/electron/resources/installerIcon.ico b/electron/resources/installerIcon.ico new file mode 100644 index 000000000..bf153e107 Binary files /dev/null and b/electron/resources/installerIcon.ico differ diff --git a/electron/resources/uninstallerIcon.ico b/electron/resources/uninstallerIcon.ico new file mode 100644 index 000000000..bf153e107 Binary files /dev/null and b/electron/resources/uninstallerIcon.ico differ diff --git a/index.html b/index.html new file mode 100644 index 000000000..4874016fb --- /dev/null +++ b/index.html @@ -0,0 +1,14 @@ + + + + + + + + Vite App + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 000000000..4f6ed1362 --- /dev/null +++ b/package.json @@ -0,0 +1,85 @@ +{ + "name": "temp_name", + "productName": "temp_name", + "private": true, + "version": "2.0.0", + "description": "", + "author": "jeffvli", + "license": "GPL-3.0", + "main": "dist-electron/main/index.js", + "scripts": { + "dev": "vite", + "build": "tsc && vite build && electron-builder", + "postinstall": "node post-install.js && electron-builder install-app-deps", + "prisma:init": "npx prisma migrate dev", + "prisma:migrate": "" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "dependencies": { + "@prisma/client": "^4.4.0", + "node-mpv": "^2.0.0-beta.2" + }, + "devDependencies": { + "@types/react": "^18.0.21", + "@types/react-dom": "^18.0.6", + "@vitejs/plugin-react": "^2.1.0", + "electron": "^21.1.0", + "electron-builder": "^23.3.3", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.55.0", + "typescript": "^4.8.4", + "vite": "^3.1.4", + "vite-electron-plugin": "^0.4.4", + "@emotion/react": "^11.10.4", + "@emotion/styled": "^11.10.4", + "@mantine/carousel": "^5.5.4", + "@mantine/core": "^5.5.4", + "@mantine/dates": "^5.5.4", + "@mantine/form": "^5.5.4", + "@mantine/hooks": "^5.5.4", + "@mantine/modals": "^5.5.4", + "@mantine/notifications": "^5.5.4", + "@mantine/spotlight": "^5.5.4", + "@tanstack/react-query": "^4.10.1", + "@typescript-eslint/eslint-plugin": "^5.39.0", + "@typescript-eslint/parser": "^5.39.0", + "ag-grid-community": "^28.2.0", + "ag-grid-react": "^28.2.0", + "axios": "^1.0.0", + "dayjs": "^1.11.5", + "electron-is-dev": "^2.0.0", + "electron-rebuild": "^3.2.9", + "embla-carousel-react": "^7.0.3", + "eslint": "^8.24.0", + "eslint-config-prettier": "^8.5.0", + "eslint-import-resolver-typescript": "^3.5.1", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jsx-a11y": "^6.6.1", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-promise": "^6.0.1", + "eslint-plugin-react": "^7.31.8", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-sort-keys-fix": "^1.1.2", + "eslint-plugin-typescript-sort-keys": "^2.1.0", + "immer": "^9.0.15", + "nanoid": "^4.0.0", + "prettier": "^2.7.1", + "prisma": "^4.4.0", + "react-virtualized-auto-sizer": "^1.0.7", + "react-window": "^1.8.7", + "react-window-infinite-loader": "^1.0.8", + "replace-in-file": "^6.3.5", + "ts-node": "^10.9.1", + "vite-plugin-electron": "^0.9.2", + "vite-plugin-electron-renderer": "^0.9.3", + "zustand": "^4.1.1" + }, + "debug": { + "env": { + "VITE_DEV_SERVER_URL": "http://127.0.0.1:7777" + } + } +} diff --git a/post-install.js b/post-install.js new file mode 100644 index 000000000..838f75a78 --- /dev/null +++ b/post-install.js @@ -0,0 +1,14 @@ +const path = require('path'); +const replace = require('replace-in-file'); + +// fix long prisma loading times caused by scanning from process.cwd(), which returns "/" when run in electron +// (thus it scans all files on the computer.) See https://github.com/prisma/prisma/issues/8484 +// solution: we get the app path from main process via sync IPC +const options = { + files: path.join(__dirname, '../release/app/node_modules/', '@prisma', 'client', 'index.js'), + from: 'findSync(process.cwd()', + to: `findSync(require("electron").ipcRenderer.sendSync('config:get-app-path')`, +}; + +const results = replace.sync(options); +console.log('build script: prisma fix', results); diff --git a/public/electron-vite-react-debug.gif b/public/electron-vite-react-debug.gif new file mode 100644 index 000000000..4f8799239 Binary files /dev/null and b/public/electron-vite-react-debug.gif differ diff --git a/public/electron-vite-react.gif b/public/electron-vite-react.gif new file mode 100644 index 000000000..a4b5da575 Binary files /dev/null and b/public/electron-vite-react.gif differ diff --git a/public/electron.png b/public/electron.png new file mode 100644 index 000000000..45c8adbea Binary files /dev/null and b/public/electron.png differ diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 000000000..de4aeddc1 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/public/node.png b/public/node.png new file mode 100644 index 000000000..1cd651947 Binary files /dev/null and b/public/node.png differ diff --git a/public/react.svg b/public/react.svg new file mode 100644 index 000000000..6b60c1042 --- /dev/null +++ b/public/react.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/public/vite.svg b/public/vite.svg new file mode 100644 index 000000000..6471ae072 --- /dev/null +++ b/public/vite.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 000000000..40076c552 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,73 @@ +import { useState } from "react"; +import electron from "/electron.png"; +import react from "/react.svg"; +import vite from "/vite.svg"; +import styles from "styles/app.module.scss"; + +const App: React.FC = () => { + const [count, setCount] = useState(0); + + window.electron.doThing(); + + return ( +
+
+
+
+ electron +
+
+ vite +
+
+ logo +
+
+

Hello Electron + Vite + React!

+

+ +

+

+ Edit App.tsx and save to test HMR updates. +

+
+ + Learn React + + {" | "} + + Vite Docs + +
+ Place static files into the /public folder + +
+
+
+
+ ); +}; + +export default App; diff --git a/src/assets/styles/app.module.scss b/src/assets/styles/app.module.scss new file mode 100644 index 000000000..7edf79a5e --- /dev/null +++ b/src/assets/styles/app.module.scss @@ -0,0 +1,65 @@ +.app { + text-align: center; + + .appHeader { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; + + .logos { + display: flex; + box-sizing: border-box; + align-items: center; + padding: 0 5vw; + width: 100%; + + .imgBox { + width: 33.33%; + + .appLogo { + pointer-events: none; + } + + @media (prefers-reduced-motion: no-preference) { + .appLogo { + animation: App-logo-spin infinite 20s linear; + } + @keyframes App-logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } + } + } + } + + button { + font-size: calc(10px + 2vmin); + } + + .appLink { + color: #61dafb; + } + + .staticPublic { + display: flex; + align-items: center; + + code { + padding: 4px 7px; + margin: 0 4px; + border-radius: 4px; + background-color: rgb(30, 30, 30, .7); + font-size: 13px; + } + } + } +} diff --git a/src/assets/styles/index.css b/src/assets/styles/index.css new file mode 100644 index 000000000..ec2585e8c --- /dev/null +++ b/src/assets/styles/index.css @@ -0,0 +1,13 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 000000000..c897ef0dc --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,21 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' +import 'styles/index.css' + +/** + * If you enables use of Node.js API in the Renderer-process + * ``` + * npm i -D vite-plugin-electron-renderer + * ``` + * @see - https://github.com/electron-vite/vite-plugin-electron/tree/main/packages/electron-renderer#electron-renderervite-serve + */ +// import './samples/node-api' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + +) + +postMessage({ payload: 'removeLoading' }, '*') diff --git a/src/samples/node-api.ts b/src/samples/node-api.ts new file mode 100644 index 000000000..db31e5fee --- /dev/null +++ b/src/samples/node-api.ts @@ -0,0 +1,13 @@ +import { lstat } from 'fs/promises' +import { cwd } from 'process' +import { ipcRenderer } from 'electron' + +ipcRenderer.on('main-process-message', (_event, ...args) => { + console.log('[Receive Main-process message]:', ...args) +}) + +lstat(cwd()).then(stats => { + console.log('[fs.lstat]', stats) +}).catch(err => { + console.error(err) +}) diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..c81e32289 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "paths": { + "@/*": ["src/*"], + "styles/*": ["src/assets/styles/*"] + }, + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} + diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 000000000..1eb1b76ee --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true + }, + "include": ["package.json", "electron"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 000000000..f62ed8298 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,51 @@ +import { rmSync } from 'fs' +import path from 'path' +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import electron from 'vite-electron-plugin' +import { customStart } from 'vite-electron-plugin/plugin' +import pkg from './package.json' + +rmSync(path.join(__dirname, 'dist-electron'), { recursive: true, force: true }) + +// https://vitejs.dev/config/ +export default defineConfig({ + resolve: { + alias: { + '@': path.join(__dirname, 'src'), + 'styles': path.join(__dirname, 'src/assets/styles'), + }, + }, + plugins: [ + react(), + electron({ + include: [ + 'electron', + 'preload', + ], + transformOptions: { + sourcemap: !!process.env.VSCODE_DEBUG, + }, + // Will start Electron via VSCode Debug + plugins: process.env.VSCODE_DEBUG + ? [customStart(debounce(() => console.log(/* For `.vscode/.debug.script.mjs` */'[startup] Electron App')))] + : undefined, + }), + ], + server: process.env.VSCODE_DEBUG ? (() => { + const url = new URL(pkg.debug.env.VITE_DEV_SERVER_URL) + return { + host: url.hostname, + port: +url.port, + } + })() : undefined, + clearScreen: false, +}) + +function debounce void>(fn: Fn, delay = 299) { + let t: NodeJS.Timeout + return ((...args) => { + clearTimeout(t) + t = setTimeout(() => fn(...args), delay) + }) as Fn +}