commit 2250f8926640c6cff0760b9bc4c0c90c46180f2a Author: Yinan Qin Date: Sat Jan 17 00:15:37 2026 +0800 Initial commit diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..2bc0b863 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,11 @@ +--- + +name: 🐞 Bug report +about: Create a report to help us improve +title: "[Bug] the title of bug report" +labels: bug +assignees: '' + +--- + +#### Describe the bug diff --git a/.github/ISSUE_TEMPLATE/help_wanted.md b/.github/ISSUE_TEMPLATE/help_wanted.md new file mode 100644 index 00000000..6fba797e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/help_wanted.md @@ -0,0 +1,10 @@ +--- +name: πŸ₯Ί Help wanted +about: Confuse about the use of electron-vue-vite +title: "[Help] the title of help wanted report" +labels: help wanted +assignees: '' + +--- + +#### Describe the problem you confuse diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..c533dfbf --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,12 @@ + + +### Description + + + +### What is the purpose of this pull request? + +- [ ] Bug fix +- [ ] New Feature +- [ ] Documentation update +- [ ] Other diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..fe250ebd --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "npm" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "monthly" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..29a1e2d1 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,47 @@ +name: Build + +on: + push: + branches: [main] + paths-ignore: + - "**.md" + - "**.spec.js" + - ".idea" + - ".vscode" + - ".dockerignore" + - "Dockerfile" + - ".gitignore" + - ".github/**" + - "!.github/workflows/build.yml" + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [macos-latest, ubuntu-latest, windows-latest] + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: Install Dependencies + run: npm install + + - name: Build Release Files + run: npm run build + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload Artifact + uses: actions/upload-artifact@v3 + with: + name: release_on_${{ matrix. os }} + path: release/ + retention-days: 5 \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..3d3b53e1 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,81 @@ +name: CI + +on: + pull_request_target: + branches: + - main + +permissions: + pull-requests: write + +jobs: + job1: + name: Check Not Allowed File Changes + runs-on: ubuntu-latest + outputs: + markdown_change: ${{ steps.filter_markdown.outputs.change }} + markdown_files: ${{ steps.filter_markdown.outputs.change_files }} + steps: + + - name: Check Not Allowed File Changes + uses: dorny/paths-filter@v2 + id: filter_not_allowed + with: + list-files: json + filters: | + change: + - 'package-lock.json' + - 'yarn.lock' + - 'pnpm-lock.yaml' + + # ref: https://github.com/github/docs/blob/main/.github/workflows/triage-unallowed-contributions.yml + - name: Comment About Changes We Can't Accept + if: ${{ steps.filter_not_allowed.outputs.change == 'true' }} + uses: actions/github-script@v6 + with: + script: | + let workflowFailMessage = "It looks like you've modified some files that we can't accept as contributions." + try { + const badFilesArr = [ + 'package-lock.json', + 'yarn.lock', + 'pnpm-lock.yaml', + ] + const badFiles = badFilesArr.join('\n- ') + const reviewMessage = `πŸ‘‹ Hey there spelunker. It looks like you've modified some files that we can't accept as contributions. The complete list of files we can't accept are:\n- ${badFiles}\n\nYou'll need to revert all of the files you changed in that list using [GitHub Desktop](https://docs.github.com/en/free-pro-team@latest/desktop/contributing-and-collaborating-using-github-desktop/managing-commits/reverting-a-commit) or \`git checkout origin/main \`. Once you get those files reverted, we can continue with the review process. :octocat:\n\nMore discussion:\n- https://github.com/electron-vite/electron-vite-vue/issues/192` + createdComment = await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.number, + body: reviewMessage, + }) + workflowFailMessage = `${workflowFailMessage} Please see ${createdComment.data.html_url} for details.` + } catch(err) { + console.log("Error creating comment.", err) + } + core.setFailed(workflowFailMessage) + + - name: Check Not Linted Markdown + if: ${{ always() }} + uses: dorny/paths-filter@v2 + id: filter_markdown + with: + list-files: shell + filters: | + change: + - added|modified: '*.md' + + + job2: + name: Lint Markdown + runs-on: ubuntu-latest + needs: job1 + if: ${{ always() && needs.job1.outputs.markdown_change == 'true' }} + steps: + - name: Checkout Code + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Lint markdown + run: npx markdownlint-cli ${{ needs.job1.outputs.markdown_files }} --ignore node_modules \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f798fbc2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +dist-electron +release +*.local + +# Editor directories and files +.vscode/.debug.env +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# lockfile +package-lock.json +pnpm-lock.yaml +yarn.lock \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..45c8cd74 --- /dev/null +++ b/.npmrc @@ -0,0 +1,6 @@ +# For electron-builder +# https://github.com/electron-userland/electron-builder/issues/6289#issuecomment-1042620422 +shamefully-hoist=true + +# For China πŸ‡¨πŸ‡³ developers +# electron_mirror=https://npmmirror.com/mirrors/electron/ diff --git a/.vscode/.debug.script.mjs b/.vscode/.debug.script.mjs new file mode 100644 index 00000000..e3674cb0 --- /dev/null +++ b/.vscode/.debug.script.mjs @@ -0,0 +1,23 @@ +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { createRequire } from 'node:module' +import { spawn } from 'node:child_process' + +const pkg = createRequire(import.meta.url)('../package.json') +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +// 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 00000000..232ead72 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "Vue.volar", + "Vue.vscode-typescript-vue-plugin" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..3d01d65a --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,53 @@ +{ + // 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": "Before Debug", + "configurations": [ + "Debug Main Process", + "Debug Renderer Process" + ], + "presentation": { + "hidden": false, + "group": "", + "order": 1 + }, + "stopAll": true + } + ], + "configurations": [ + { + "name": "Debug Main Process", + "type": "node", + "request": "launch", + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron", + "windows": { + "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd" + }, + "runtimeArgs": [ + "--remote-debugging-port=9229", + "." + ], + "envFile": "${workspaceFolder}/.vscode/.debug.env", + "console": "integratedTerminal" + }, + { + "name": "Debug Renderer Process", + "port": 9229, + "request": "attach", + "type": "chrome", + "timeout": 60000, + "skipFiles": [ + "/**", + "${workspaceRoot}/node_modules/**", + "${workspaceRoot}/dist-electron/**", + // Skip files in host(VITE_DEV_SERVER_URL) + "http://127.0.0.1:3344/**" + ] + }, + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..1e3e2cde --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,13 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib", + "typescript.tsc.autoDetect": "off", + "json.schemas": [ + { + "fileMatch": [ + "/*electron-builder.json5", + "/*electron-builder.json" + ], + "url": "https://json.schemastore.org/electron-builder" + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..adb5c587 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,31 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "Before Debug", + "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, + "beginsPattern": "^.*VITE v.* ready in \\d* ms.*$", + "endsPattern": "^.*\\[startup\\] Electron App.*$" + } + } + } + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..db6dbaa1 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,33 @@ +## 2022-10-03 + +[v2.1.0](https://github.com/electron-vite/electron-vite-vue/pull/267) + +- `vite-electron-plugin` is Fast, and WYSIWYG. 🌱 +- last-commit: db2e830 v2.1.0: use `vite-electron-plugin` instead `vite-plugin-electron` + +## 2022-06-04 + +[v2.0.0](https://github.com/electron-vite/electron-vite-vue/pull/156) + +- πŸ–– Based on the `vue-ts` template created by `npm create vite`, integrate `vite-plugin-electron` +- ⚑️ More simplify, is in line with Vite project structure +- last-commit: a15028a (HEAD -> main) feat: hoist `process.env` + +## 2022-01-30 + +[v1.0.0](https://github.com/electron-vite/electron-vite-vue/releases/tag/v1.0.0) + +- ⚑️ Main、Renderer、preload, all built with vite + +## 2022-01-27 +- Refactor the scripts part. +- Remove `configs` directory. + +## 2021-11-11 +- Refactor the project. Use vite.config.ts build `Main-process`, `Preload-script` and `Renderer-process` alternative rollup. +- Scenic `Vue>=3.2.13`, `@vue/compiler-sfc` is no longer necessary. +- If you prefer Rollup, Use rollup branch. + +```bash +Error: @vitejs/plugin-vue requires vue (>=3.2.13) or @vue/compiler-sfc to be present in the dependency tree. +``` diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..22edc0e9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 θ‰ιž‹ζ²‘ε· + +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 00000000..65e44d60 --- /dev/null +++ b/README.md @@ -0,0 +1,79 @@ +# electron-vite-vue + +πŸ₯³ Really simple `Electron` + `Vue` + `Vite` boilerplate. + + + + + + +[![GitHub Build](https://github.com/electron-vite/electron-vite-vue/actions/workflows/build.yml/badge.svg)](https://github.com/electron-vite/electron-vite-vue/actions/workflows/build.yml) +[![GitHub Discord](https://img.shields.io/badge/chat-discord-blue?logo=discord)](https://discord.gg/sRqjYpEAUK) + +## Features + +πŸ“¦ Out of the box +🎯 Based on the official [template-vue-ts](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-vue-ts), less invasive +🌱 Extensible, really simple directory structure +πŸ’ͺ Support using Node.js API in Electron-Renderer +πŸ”© Support C/C++ native addons +πŸ–₯ It's easy to implement multiple windows + +## Quick Setup + +```sh +# clone the project +git clone https://github.com/electron-vite/electron-vite-vue.git + +# enter the project directory +cd electron-vite-vue + +# install dependency +npm install + +# develop +npm run dev +``` + +## Debug + +![electron-vite-react-debug.gif](https://github.com/electron-vite/electron-vite-react/blob/main/electron-vite-react-debug.gif?raw=true) + +## Directory + +```diff ++ β”œβ”€β”¬ electron ++ β”‚ β”œβ”€β”¬ main ++ β”‚ β”‚ └── index.ts entry of Electron-Main ++ β”‚ └─┬ preload ++ β”‚ └── index.ts entry of Preload-Scripts + β”œβ”€β”¬ src + β”‚ └── main.ts entry of Electron-Renderer + β”œβ”€β”€ index.html + β”œβ”€β”€ package.json + └── vite.config.ts +``` + + + +## FAQ + +- [C/C++ addons, Node.js modules - Pre-Bundling](https://github.com/electron-vite/vite-plugin-electron-renderer#dependency-pre-bundling) +- [dependencies vs devDependencies](https://github.com/electron-vite/vite-plugin-electron-renderer#dependencies-vs-devdependencies) diff --git a/electron-builder.json5 b/electron-builder.json5 new file mode 100644 index 00000000..bb5fb9ab --- /dev/null +++ b/electron-builder.json5 @@ -0,0 +1,43 @@ +// @see https://www.electron.build/configuration/configuration +{ + "$schema": "https://raw.githubusercontent.com/electron-userland/electron-builder/master/packages/app-builder-lib/scheme.json", + "appId": "YourAppID", + "asar": true, + "productName": "YourAppName", + "directories": { + "output": "release/${version}" + }, + "files": [ + "dist", + "dist-electron" + ], + "mac": { + "target": [ + "dmg" + ], + "artifactName": "${productName}-Mac-${version}-Installer.${ext}" + }, + "win": { + "target": [ + { + "target": "nsis", + "arch": [ + "x64" + ] + } + ], + "artifactName": "${productName}-Windows-${version}-Setup.${ext}" + }, + "nsis": { + "oneClick": false, + "perMachine": false, + "allowToChangeInstallationDirectory": true, + "deleteAppDataOnUninstall": false + }, + "linux": { + "target": [ + "AppImage" + ], + "artifactName": "${productName}-Linux-${version}.${ext}" + } +} diff --git a/electron-vite-vue.gif b/electron-vite-vue.gif new file mode 100644 index 00000000..66a14af6 Binary files /dev/null and b/electron-vite-vue.gif differ diff --git a/electron/electron-env.d.ts b/electron/electron-env.d.ts new file mode 100644 index 00000000..daa1225e --- /dev/null +++ b/electron/electron-env.d.ts @@ -0,0 +1,23 @@ +/// + +declare namespace NodeJS { + interface ProcessEnv { + VSCODE_DEBUG?: 'true' + /** + * The built directory structure + * + * ```tree + * β”œβ”€β”¬ dist-electron + * β”‚ β”œβ”€β”¬ main + * β”‚ β”‚ └── index.js > Electron-Main + * β”‚ └─┬ preload + * β”‚ └── index.mjs > Preload-Scripts + * β”œβ”€β”¬ dist + * β”‚ └── index.html > Electron-Renderer + * ``` + */ + APP_ROOT: string + /** /dist/ or /public/ */ + VITE_PUBLIC: string + } +} diff --git a/electron/main/index.ts b/electron/main/index.ts new file mode 100644 index 00000000..a95a3a26 --- /dev/null +++ b/electron/main/index.ts @@ -0,0 +1,120 @@ +import { app, BrowserWindow, shell, ipcMain } from 'electron' +import { createRequire } from 'node:module' +import { fileURLToPath } from 'node:url' +import path from 'node:path' +import os from 'node:os' + +const require = createRequire(import.meta.url) +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +// The built directory structure +// +// β”œβ”€β”¬ dist-electron +// β”‚ β”œβ”€β”¬ main +// β”‚ β”‚ └── index.js > Electron-Main +// β”‚ └─┬ preload +// β”‚ └── index.mjs > Preload-Scripts +// β”œβ”€β”¬ dist +// β”‚ └── index.html > Electron-Renderer +// +process.env.APP_ROOT = path.join(__dirname, '../..') + +export const MAIN_DIST = path.join(process.env.APP_ROOT, 'dist-electron') +export const RENDERER_DIST = path.join(process.env.APP_ROOT, 'dist') +export const VITE_DEV_SERVER_URL = process.env.VITE_DEV_SERVER_URL + +process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL + ? path.join(process.env.APP_ROOT, 'public') + : RENDERER_DIST + +// Disable GPU Acceleration for Windows 7 +if (os.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 +const preload = path.join(__dirname, '../preload/index.mjs') +const indexHtml = path.join(RENDERER_DIST, 'index.html') + +async function createWindow() { + win = new BrowserWindow({ + title: 'Main window', + icon: path.join(process.env.VITE_PUBLIC, 'favicon.ico'), + webPreferences: { + preload, + // Warning: Enable nodeIntegration and disable contextIsolation is not secure in production + // nodeIntegration: true, + + // Consider using contextBridge.exposeInMainWorld + // Read more on https://www.electronjs.org/docs/latest/tutorial/context-isolation + // contextIsolation: false, + }, + }) + + if (VITE_DEV_SERVER_URL) { // #298 + win.loadURL(VITE_DEV_SERVER_URL) + // Open devTool if the app is not packaged + win.webContents.openDevTools() + } else { + win.loadFile(indexHtml) + } + + // 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' } + }) + // win.webContents.on('will-navigate', (event, url) => { }) #344 +} + +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', (_, arg) => { + const childWindow = new BrowserWindow({ + webPreferences: { + preload, + nodeIntegration: true, + contextIsolation: false, + }, + }) + + if (VITE_DEV_SERVER_URL) { + childWindow.loadURL(`${VITE_DEV_SERVER_URL}#${arg}`) + } else { + childWindow.loadFile(indexHtml, { hash: arg }) + } +}) diff --git a/electron/preload/index.ts b/electron/preload/index.ts new file mode 100644 index 00000000..53b585fa --- /dev/null +++ b/electron/preload/index.ts @@ -0,0 +1,118 @@ +import { ipcRenderer, contextBridge } from 'electron' + +// --------- Expose some API to the Renderer process --------- +contextBridge.exposeInMainWorld('ipcRenderer', { + on(...args: Parameters) { + const [channel, listener] = args + return ipcRenderer.on(channel, (event, ...args) => listener(event, ...args)) + }, + off(...args: Parameters) { + const [channel, ...omit] = args + return ipcRenderer.off(channel, ...omit) + }, + send(...args: Parameters) { + const [channel, ...omit] = args + return ipcRenderer.send(channel, ...omit) + }, + invoke(...args: Parameters) { + const [channel, ...omit] = args + return ipcRenderer.invoke(channel, ...omit) + }, + + // You can expose other APTs you need here. + // ... +}) + +// --------- Preload scripts loading --------- +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) diff --git a/index.html b/index.html new file mode 100644 index 00000000..b9b222e4 --- /dev/null +++ b/index.html @@ -0,0 +1,14 @@ + + + + + + + + Electron + Vite + Vue + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 00000000..8c2448a9 --- /dev/null +++ b/package.json @@ -0,0 +1,38 @@ +{ + "name": "electron-vue-vite", + "version": "28.1.0", + "main": "dist-electron/main/index.js", + "description": "Really simple Electron + Vue + Vite boilerplate.", + "author": "θ‰ιž‹ζ²‘ε· <308487730@qq.com>", + "license": "MIT", + "private": true, + "keywords": [ + "electron", + "rollup", + "vite", + "vue3", + "vue" + ], + "debug": { + "env": { + "VITE_DEV_SERVER_URL": "http://127.0.0.1:3344/" + } + }, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc --noEmit && vite build && electron-builder", + "preview": "vite preview" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.4", + "electron": "^29.1.1", + "electron-builder": "^24.13.3", + "typescript": "^5.4.2", + "vite": "^5.1.5", + "vite-plugin-electron": "^0.28.4", + "vite-plugin-electron-renderer": "^0.14.5", + "vue": "^3.4.21", + "vue-tsc": "^2.0.6" + } +} diff --git a/public/logo.svg b/public/logo.svg new file mode 100644 index 00000000..96fc008b --- /dev/null +++ b/public/logo.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 00000000..03414cee --- /dev/null +++ b/src/App.vue @@ -0,0 +1,49 @@ + + + + + diff --git a/src/assets/electron.svg b/src/assets/electron.svg new file mode 100644 index 00000000..1c5cccbe --- /dev/null +++ b/src/assets/electron.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/vite.svg b/src/assets/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/src/assets/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/vue.svg b/src/assets/vue.svg new file mode 100644 index 00000000..770e9d33 --- /dev/null +++ b/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/HelloWorld.vue b/src/components/HelloWorld.vue new file mode 100644 index 00000000..52309103 --- /dev/null +++ b/src/components/HelloWorld.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/src/demos/ipc.ts b/src/demos/ipc.ts new file mode 100644 index 00000000..ba4daa0c --- /dev/null +++ b/src/demos/ipc.ts @@ -0,0 +1,4 @@ + +window.ipcRenderer.on('main-process-message', (_event, ...args) => { + console.log('[Receive Main-process message]:', ...args) +}) diff --git a/src/demos/node.ts b/src/demos/node.ts new file mode 100644 index 00000000..277e6a34 --- /dev/null +++ b/src/demos/node.ts @@ -0,0 +1,8 @@ +import { lstat } from 'node:fs/promises' +import { cwd } from 'node:process' + +lstat(cwd()).then(stats => { + console.log('[fs.lstat]', stats) +}).catch(err => { + console.error(err) +}) diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 00000000..4a868581 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,14 @@ +import { createApp } from 'vue' +import App from './App.vue' + +import './style.css' + +import './demos/ipc' +// If you want use Node.js, the`nodeIntegration` needs to be enabled in the Main process. +// import './demos/node' + +createApp(App) + .mount('#app') + .$nextTick(() => { + postMessage({ payload: 'removeLoading' }, '*') + }) diff --git a/src/style.css b/src/style.css new file mode 100644 index 00000000..64aafe81 --- /dev/null +++ b/src/style.css @@ -0,0 +1,90 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +code { + background-color: #1a1a1a; + padding: 2px 4px; + margin: 0 4px; + border-radius: 4px; +} + +.card { + padding: 2em; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } + code { + background-color: #f9f9f9; + } +} diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 00000000..788532e7 --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1,12 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} + +interface Window { + // expose in the `electron/preload/index.ts` + ipcRenderer: import('electron').IpcRenderer +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..6d0f5ee4 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "jsx": "preserve", + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext", "DOM"], + "skipLibCheck": true, + "noEmit": true + }, + "include": ["src"], + "references": [ + { "path": "./tsconfig.node.json" } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 00000000..ed1b5866 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts", "package.json", "electron"] +} diff --git a/vite.config.flat.txt b/vite.config.flat.txt new file mode 100644 index 00000000..1ab23d59 --- /dev/null +++ b/vite.config.flat.txt @@ -0,0 +1,76 @@ +import { rmSync } from 'node:fs' +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import electron from 'vite-plugin-electron' +import renderer from 'vite-plugin-electron-renderer' +import pkg from './package.json' + +// https://vitejs.dev/config/ +export default defineConfig(({ command }) => { + rmSync('dist-electron', { recursive: true, force: true }) + + const isServe = command === 'serve' + const isBuild = command === 'build' + const sourcemap = isServe || !!process.env.VSCODE_DEBUG + + return { + plugins: [ + vue(), + electron([ + { + // Main process entry file of the Electron App. + entry: 'electron/main/index.ts', + onstart({ startup }) { + if (process.env.VSCODE_DEBUG) { + console.log(/* For `.vscode/.debug.script.mjs` */'[startup] Electron App') + } else { + startup() + } + }, + vite: { + build: { + sourcemap, + minify: isBuild, + outDir: 'dist-electron/main', + rollupOptions: { + // Some third-party Node.js libraries may not be built correctly by Vite, especially `C/C++` addons, + // we can use `external` to exclude them to ensure they work correctly. + // Others need to put them in `dependencies` to ensure they are collected into `app.asar` after the app is built. + // Of course, this is not absolute, just this way is relatively simple. :) + external: Object.keys('dependencies' in pkg ? pkg.dependencies : {}), + }, + }, + }, + }, + { + entry: 'electron/preload/index.ts', + onstart({ reload }) { + // Notify the Renderer process to reload the page when the Preload scripts build is complete, + // instead of restarting the entire Electron App. + reload() + }, + vite: { + build: { + sourcemap: sourcemap ? 'inline' : undefined, // #332 + minify: isBuild, + outDir: 'dist-electron/preload', + rollupOptions: { + external: Object.keys('dependencies' in pkg ? pkg.dependencies : {}), + }, + }, + }, + } + ]), + // Use Node.js API in the Renderer process + renderer(), + ], + server: process.env.VSCODE_DEBUG && (() => { + const url = new URL(pkg.debug.env.VITE_DEV_SERVER_URL) + return { + host: url.hostname, + port: +url.port, + } + })(), + clearScreen: false, + } +}) diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 00000000..a3e1ed86 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,74 @@ +import fs from 'node:fs' +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import electron from 'vite-plugin-electron/simple' +import pkg from './package.json' + +// https://vitejs.dev/config/ +export default defineConfig(({ command }) => { + fs.rmSync('dist-electron', { recursive: true, force: true }) + + const isServe = command === 'serve' + const isBuild = command === 'build' + const sourcemap = isServe || !!process.env.VSCODE_DEBUG + + return { + plugins: [ + vue(), + electron({ + main: { + // Shortcut of `build.lib.entry` + entry: 'electron/main/index.ts', + onstart({ startup }) { + if (process.env.VSCODE_DEBUG) { + console.log(/* For `.vscode/.debug.script.mjs` */'[startup] Electron App') + } else { + startup() + } + }, + vite: { + build: { + sourcemap, + minify: isBuild, + outDir: 'dist-electron/main', + rollupOptions: { + // Some third-party Node.js libraries may not be built correctly by Vite, especially `C/C++` addons, + // we can use `external` to exclude them to ensure they work correctly. + // Others need to put them in `dependencies` to ensure they are collected into `app.asar` after the app is built. + // Of course, this is not absolute, just this way is relatively simple. :) + external: Object.keys('dependencies' in pkg ? pkg.dependencies : {}), + }, + }, + }, + }, + preload: { + // Shortcut of `build.rollupOptions.input`. + // Preload scripts may contain Web assets, so use the `build.rollupOptions.input` instead `build.lib.entry`. + input: 'electron/preload/index.ts', + vite: { + build: { + sourcemap: sourcemap ? 'inline' : undefined, // #332 + minify: isBuild, + outDir: 'dist-electron/preload', + rollupOptions: { + external: Object.keys('dependencies' in pkg ? pkg.dependencies : {}), + }, + }, + }, + }, + // Ployfill the Electron and Node.js API for Renderer process. + // If you want use Node.js in Renderer process, the `nodeIntegration` needs to be enabled in the Main process. + // See πŸ‘‰ https://github.com/electron-vite/vite-plugin-electron-renderer + renderer: {}, + }), + ], + server: process.env.VSCODE_DEBUG && (() => { + const url = new URL(pkg.debug.env.VITE_DEV_SERVER_URL) + return { + host: url.hostname, + port: +url.port, + } + })(), + clearScreen: false, + } +})