Merge pull request #34 from electron-vite/v2.0.0

V2.0.0
This commit is contained in:
草鞋没号 2022-06-07 19:08:56 +08:00 committed by GitHub
commit 99d473948b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 277 additions and 11549 deletions

12
.editorconfig Normal file
View File

@ -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

97
.gitignore vendored
View File

@ -4,86 +4,25 @@ logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
.debug.env
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# ----
node_modules
dist
**/.tmp
release
.DS_Store
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

View File

@ -2,13 +2,19 @@ import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'
import { createRequire } from 'module'
import { spawn } from 'child_process'
const pkg = createRequire(import.meta.url)('../package.json')
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.env).map(([key, val]) => `${key}=${val}`)
fs.writeFileSync(path.join(__dirname, '.debug.env'), envContent.join('\n'))
// bootstrap
import('../scripts/watch.mjs?debug=vscode')
spawn(
process.platform === 'win32' ? 'npm.cmd' : 'npm',
['run', 'dev'],
{ stdio: 'inherit' },
)

10
.vscode/extensions.json vendored Normal file
View File

@ -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",
]
}

2
.vscode/launch.json vendored
View File

@ -30,7 +30,7 @@
},
"runtimeArgs": [
"--remote-debugging-port=9229",
"${workspaceRoot}/dist/main/index.cjs"
"${workspaceRoot}/dist/electron/main/index.js"
],
"envFile": "${workspaceFolder}/.vscode/.debug.env"
},

122
README.md
View File

@ -1,4 +1,4 @@
# vite-react-electron
# electron-vite-react
![GitHub stars](https://img.shields.io/github/stars/caoxiemeihao/vite-react-electron?color=fa6470&style=flat)
![GitHub issues](https://img.shields.io/github/issues/caoxiemeihao/vite-react-electron?color=d8b22d&style=flat)
@ -11,7 +11,7 @@
This is a `Vite`-integrated `Electron` template built with simplification in mind.
The repo contains only the most basic files, dependencies and functionalities to ensure flexibility for various scenarios.
The repo contains only the most basic files, dependencies and functionalities to ensure flexibility for various scenarios.
You need a basic understanding of `Electron` and `Vite` to get started. But that's not mandatory - you can learn almost all the details by reading through the source code. Trust me, this repo is not that complex. 😋
@ -21,108 +21,76 @@ You need a basic understanding of `Electron` and `Vite` to get started. But that
npm create electron-vite
```
![electron-vite-react.gif](https://github.com/electron-vite/electron-vite-react/blob/main/packages/renderer/public/electron-vite-react.gif?raw=true)
![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/packages/renderer/public/electron-vite-react-debug.gif?raw=true)
<!--
```sh
# clone the project
git clone https://github.com/caoxiemeihao/vite-react-electron.git
# open the project directory
cd vite-react-electron
# install dependencies
npm install
# start the application
npm run dev
# make a production build
npm run build
```
-->
![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
Once `dev` or `build` npm-script is executed, the `dist` folder will be generated. It has the same structure as the `packages` folder, the purpose of this design is to ensure the correct path calculation.
Once `dev` or `build` npm-script is executed, the `dist` folder will be generated. It has the same structure as the project, the purpose of this design is to ensure the correct path calculation.
```tree
├── build 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
|
├── dist Generated after build according to the "packages" directory
| ├── main
| ├── preload
| └── renderer
├── electron Electron-related code
| ├── main Main-process source code
| ├── preload Preload-script 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
|
├── release Generated after production build, contains executables
| └──{version}
| ├── win-unpacked Contains unpacked application executable
| └── Setup.exe Installer for the application
| ├── {os}-unpacked Contains unpacked application executable
| └── Setup.{ext} Installer for the application
|
├── scripts
| ├── build.mjs Develop script -> npm run build
| └── watch.mjs Develop script -> npm run dev
|
├── packages
| ├── main Main-process source code
| | └── vite.config.ts
| ├── preload Preload-script source code
| | └── vite.config.ts
| └── renderer Renderer-process source code
| └── vite.config.ts
├── public Static assets
└── src Renderer source code, your React application
```
## Use Electron and NodeJS API
> 🚧 By default, Electron doesn't support the use of API related to Electron and NodeJS in the Renderer process, but someone might need to use it. If so, you can see the template 👉 **[electron-vite-boilerplate](https://github.com/caoxiemeihao/electron-vite-boilerplate)**
> 🚧 By default, Electron doesn't support the use of API related to Electron and NodeJS in the Renderer process, but someone might need to use it. If so, you can see the template 👉 **[electron-vite-boilerplate](https://github.com/electron-vite/electron-vite-boilerplate)**
#### Invoke Electron and NodeJS API in `Preload-script`
- **packages/preload/index.ts**
- **electron/preload/index.ts**
```typescript
import fs from "fs"
import { contextBridge, ipcRenderer } from "electron"
```typescript
import fs from "fs";
import { contextBridge, ipcRenderer } from "electron";
// --------- Expose some API to Renderer-process. ---------
contextBridge.exposeInMainWorld("fs", fs)
contextBridge.exposeInMainWorld("ipcRenderer", ipcRenderer)
```
// --------- Expose some API to Renderer-process. ---------
contextBridge.exposeInMainWorld("fs", fs);
contextBridge.exposeInMainWorld("ipcRenderer", ipcRenderer);
```
- **packages/renderer/src/global.d.ts**
- **src/global.d.ts**
```typescript
// Defined in the window
interface Window {
fs: typeof import("fs")
ipcRenderer: import("electron").IpcRenderer
}
```
```typescript
// Defined in the window
interface Window {
fs: typeof import("fs");
ipcRenderer: import("electron").IpcRenderer;
}
```
- **packages/renderer/src/main.ts**
- **src/main.ts**
```typescript
// Use Electron and NodeJS API in the Renderer-process
console.log("fs", window.fs)
console.log("ipcRenderer", window.ipcRenderer)
```
```typescript
// Use Electron and NodeJS API in the Renderer-process
console.log("fs", window.fs);
console.log("ipcRenderer", window.ipcRenderer);
```
## Use SerialPort, SQLite3, or other node-native addons in the Main-process
- First, you need to make sure that the dependencies in the `package.json` are NOT in the "devDependencies". Because the project will need them after packaged.
- Main-process, Preload-script are also built with Vite, and they're built as [build.lib](https://vitejs.dev/config/#build-lib).
So they just need to configure Rollup.
**Click to see more** 👉 [packages/main/vite.config.ts](https://github.com/caoxiemeihao/vite-react-electron/blob/main/packages/main/vite.config.ts)
So they just need to configure Rollup.
```js
export default {
@ -138,7 +106,7 @@ export default {
external: ["serialport", "sqlite3"],
},
},
}
};
```
## `dependencies` vs `devDependencies`
@ -148,9 +116,3 @@ export default {
- Like [serialport](https://www.npmjs.com/package/serialport), [sqlite3](https://www.npmjs.com/package/sqlite3) they are node-native modules and should be placed in `dependencies`. In addition, Vite will not build them, but treat them as external modules.
- Dependencies like [Vue](https://www.npmjs.com/package/vue) and [React](https://www.npmjs.com/package/react), which are pure javascript modules that can be built with Vite, can be placed in `devDependencies`. This reduces the size of the application.
<!--
## Result
<img width="400px" src="https://raw.githubusercontent.com/caoxiemeihao/blog/main/vite-react-electron/react-win.png" />
-->

View File

@ -2,36 +2,36 @@
* @see https://www.electron.build/configuration/configuration
*/
{
"appId": "YourAppID",
"productName": "YourAppName",
"copyright": "Copyright © 2022 ${author}",
"asar": true,
"directories": {
"output": "release/${version}",
"buildResources": "resources"
appId: "YourAppID",
productName: "YourAppName",
copyright: "Copyright © 2022 ${author}",
asar: true,
directories: {
output: "release/${version}",
buildResources: "electron/resources",
},
"files": ["dist"],
"win": {
"target": [
files: ["dist"],
win: {
target: [
{
"target": "nsis",
"arch": ["x64"]
}
target: "nsis",
arch: ["x64"],
},
],
"artifactName": "${productName}-${version}-Setup.${ext}"
artifactName: "${productName}-${version}-Setup.${ext}",
},
"nsis": {
"oneClick": false,
"perMachine": false,
"allowToChangeInstallationDirectory": true,
"deleteAppDataOnUninstall": false
nsis: {
oneClick: false,
perMachine: false,
allowToChangeInstallationDirectory: true,
deleteAppDataOnUninstall: false,
},
"mac": {
"target": ["dmg"],
"artifactName": "${productName}-${version}-Installer.${ext}"
mac: {
target: ["dmg"],
artifactName: "${productName}-${version}-Installer.${ext}",
},
"linux": {
"target": ["AppImage"],
"artifactName": "${productName}-${version}-Installer.${ext}"
}
}
linux: {
target: ["AppImage"],
artifactName: "${productName}-${version}-Installer.${ext}",
},
}

View File

@ -1,8 +1,6 @@
import { app, BrowserWindow, shell } from 'electron'
import { release } from 'os'
import { join } from 'path'
import './samples/electron-store'
import './samples/npm-esm-packages'
// Disable GPU Acceleration for Windows 7
if (release().startsWith('6.1')) app.disableHardwareAcceleration()
@ -14,23 +12,27 @@ if (!app.requestSingleInstanceLock()) {
app.quit()
process.exit(0)
}
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'
let win: BrowserWindow | null = null
// Here you can add more preload scripts
const splash = join(__dirname, '../preload/splash.js')
// 🚧 Use ['ENV_NAME'] to avoid vite:define plugin
const url = `http://${process.env['VITE_DEV_SERVER_HOST']}:${process.env['VITE_DEV_SERVER_PORT']}`
async function createWindow() {
win = new BrowserWindow({
title: 'Main window',
webPreferences: {
preload: join(__dirname, '../preload/index.cjs')
preload: splash,
nodeIntegration: true,
contextIsolation: false,
},
})
if (app.isPackaged) {
win.loadFile(join(__dirname, '../renderer/index.html'))
win.loadFile(join(__dirname, '../../index.html'))
} else {
// 🚧 Use ['ENV_NAME'] avoid vite:define plugin
const url = `http://${process.env['VITE_DEV_SERVER_HOST']}:${process.env['VITE_DEV_SERVER_PORT']}`
win.loadURL(url)
// win.webContents.openDevTools()
}

View File

@ -1,10 +1,37 @@
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
*/
export function useLoading() {
function useLoading() {
const className = `loaders-css__square-spin`
const styleContent = `
@keyframes square-spin {
@ -43,25 +70,18 @@ export function useLoading() {
return {
appendLoading() {
safe.append(document.head, oStyle)
safe.append(document.body, oDiv)
safeDOM.append(document.head, oStyle)
safeDOM.append(document.body, oDiv)
},
removeLoading() {
safe.remove(document.head, oStyle)
safe.remove(document.body, oDiv)
safeDOM.remove(document.head, oStyle)
safeDOM.remove(document.body, oDiv)
},
}
}
const safe = {
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)
}
},
}
// ----------------------------------------------------------------------
const { appendLoading, removeLoading } = useLoading()
window.removeLoading = removeLoading
domReady().then(appendLoading)

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -4,10 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/assets/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' 'unsafe-inline';"
/>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
<title>Vite App</title>
</head>
<body>

10854
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,15 @@
{
"name": "vite-react-electron",
"name": "electron-vite-react",
"productName": "Electron",
"private": true,
"version": "1.0.0",
"version": "2.0.0",
"description": "Vite React Electron boilerplate.",
"author": "草鞋没号 <308487730@qq.com>",
"license": "MIT",
"main": "dist/main/index.cjs",
"main": "dist/electron/main/index.js",
"scripts": {
"dev": "node scripts/watch.mjs",
"build": "tsc --noEmit -p packages/renderer/tsconfig.json && node scripts/build.mjs && electron-builder"
"dev": "vite",
"build": "tsc && vite build && electron-builder"
},
"engines": {
"node": ">=14.17.0"
@ -18,20 +18,17 @@
"electron-store": "^8.0.1"
},
"devDependencies": {
"@types/react": "^18.0.9",
"@types/react-dom": "^18.0.4",
"@types/react": "^18.0.12",
"@types/react-dom": "^18.0.5",
"@vitejs/plugin-react": "^1.3.2",
"electron": "^18.2.4",
"electron": "^19.0.3",
"electron-builder": "^23.0.3",
"execa": "^6.1.0",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"sass": "^1.51.0",
"typescript": "^4.6.4",
"vite": "^2.9.9",
"vite-plugin-esmodule": "^1.2.5",
"vite-plugin-optimizer": "^1.3.3",
"vite-plugin-resolve": "^2.1.1"
"sass": "^1.52.2",
"typescript": "^4.7.3",
"vite": "^2.9.10",
"vite-plugin-electron": "^0.4.7"
},
"env": {
"VITE_DEV_SERVER_HOST": "127.0.0.1",

View File

@ -1,19 +0,0 @@
/**
* Example of 'electron-store' usage.
*/
import { ipcMain } from 'electron'
import Store from 'electron-store'
/**
* Expose 'electron-store' to Renderer-process through 'ipcMain.handle'
*/
const store = new Store()
ipcMain.handle(
'electron-store',
async (_event, methodSign: string, ...args: any[]) => {
if (typeof (store as any)[methodSign] === 'function') {
return (store as any)[methodSign](...args)
}
return (store as any)[methodSign]
}
)

View File

@ -1,7 +0,0 @@
import { execa } from 'execa'
(async () => {
const { stdout } = await execa('echo', ['unicorns'])
// console.log(stdout) // unicorns
})()

View File

@ -1,31 +0,0 @@
import { builtinModules } from 'module'
import { defineConfig } from 'vite'
import esmodule from 'vite-plugin-esmodule'
import pkg from '../../package.json'
export default defineConfig({
root: __dirname,
plugins: [
esmodule([
'execa',
]),
],
build: {
outDir: '../../dist/main',
emptyOutDir: true,
minify: process.env./* from mode option */NODE_ENV === 'production',
sourcemap: true,
lib: {
entry: 'index.ts',
formats: ['cjs'],
fileName: () => '[name].cjs',
},
rollupOptions: {
external: [
'electron',
...builtinModules,
...Object.keys(pkg.dependencies || {}),
],
},
},
})

View File

@ -1,36 +0,0 @@
import fs from 'fs'
import { contextBridge, ipcRenderer } from 'electron'
import { domReady } from './utils'
import { useLoading } from './loading'
const { appendLoading, removeLoading } = useLoading()
;(async () => {
await domReady()
appendLoading()
})()
// --------- Expose some API to the Renderer process. ---------
contextBridge.exposeInMainWorld('fs', fs)
contextBridge.exposeInMainWorld('removeLoading', removeLoading)
contextBridge.exposeInMainWorld('ipcRenderer', withPrototype(ipcRenderer))
// `exposeInMainWorld` can't detect attributes and methods of `prototype`, manually patching it.
function withPrototype(obj: Record<string, any>) {
const protos = Object.getPrototypeOf(obj)
for (const [key, value] of Object.entries(protos)) {
if (Object.prototype.hasOwnProperty.call(obj, key)) continue
if (typeof value === 'function') {
// Some native APIs, like `NodeJS.EventEmitter['on']`, don't work in the Renderer process. Wrapping them into a function.
obj[key] = function (...args: any) {
return value.call(obj, ...args)
}
} else {
obj[key] = value
}
}
return obj
}

View File

@ -1,16 +0,0 @@
/** Document ready */
export const 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)
}
})
}
})
}

View File

@ -1,31 +0,0 @@
import { join } from 'path'
import { builtinModules } from 'module'
import { defineConfig } from 'vite'
import pkg from '../../package.json'
export default defineConfig({
root: __dirname,
build: {
outDir: '../../dist/preload',
emptyOutDir: true,
minify: process.env./* from mode option */NODE_ENV === 'production',
// https://github.com/caoxiemeihao/electron-vue-vite/issues/61
sourcemap: 'inline',
rollupOptions: {
input: {
// multiple entry
index: join(__dirname, 'index.ts'),
},
output: {
format: 'cjs',
entryFileNames: '[name].cjs',
manualChunks: {},
},
external: [
'electron',
...builtinModules,
...Object.keys(pkg.dependencies || {}),
],
},
},
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -1,16 +0,0 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App'
import './samples/electron-store'
import './samples/preload-module'
import './styles/index.css'
const root = createRoot(document.getElementById('root')!)
root.render(
<StrictMode>
<App />
</StrictMode>
)
window.removeLoading()

View File

@ -1,31 +0,0 @@
// Usage of 'electron-store'
const store = {
async get(key: string) {
const { invoke } = window.ipcRenderer
let value = await invoke('electron-store', 'get', key)
try {
value = JSON.parse(value)
} finally {
return value
}
},
async set(key: string, value: any) {
const { invoke } = window.ipcRenderer
let val = value
try {
if (value && typeof value === 'object') {
val = JSON.stringify(value)
}
} finally {
await invoke('electron-store', 'set', key, val)
}
},
};
(async () => {
await store.set('Date.now', Date.now())
console.log('electron-store ->', 'Date.now:', await store.get('Date.now'))
console.log('electron-store ->', 'path:', await window.ipcRenderer.invoke('electron-store', 'path'))
})();
export { }

View File

@ -1,9 +0,0 @@
console.log('fs', window.fs)
console.log('ipcRenderer', window.ipcRenderer)
// Usage of ipcRenderer.on
window.ipcRenderer.on('main-process-message', (_event, ...args) => {
console.log('[Receive Main-process message]:', ...args)
})
export default {}

View File

@ -1,21 +0,0 @@
{
"extends": "../../paths.json",
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"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"]
}

View File

@ -1,144 +0,0 @@
import { join } from 'path'
import { builtinModules } from 'module'
import { defineConfig, Plugin } from 'vite'
import react from '@vitejs/plugin-react'
import optimizer from 'vite-plugin-optimizer'
import resolve, { lib2esm } from 'vite-plugin-resolve'
import pkg from '../../package.json'
/**
* @see https://vitejs.dev/config/
*/
export default defineConfig({
mode: process.env.NODE_ENV,
root: __dirname,
plugins: [
react(),
electron(),
resolve({
/**
* Here you can resolve some CommonJs module.
* Or some Node.js native modules they may not be built correctly by vite.
* At the same time, these modules should be put in `dependencies`,
* because they will not be built by vite, but will be packaged into `app.asar` by electron-builder
*/
// ESM format code snippets
'electron-store': 'export default require("electron-store");',
/**
* Node.js native module
* Use lib2esm() to easy to convert ESM
* Equivalent to
*
* ```js
* sqlite3: () => `
* const _M_ = require('sqlite3');
* const _D_ = _M_.default || _M_;
* export { _D_ as default }
* `
* ```
*/
sqlite3: lib2esm('sqlite3', { format: 'cjs' }),
serialport: lib2esm(
// CJS lib name
'serialport',
// export memebers
[
'SerialPort',
'SerialPortMock',
],
{ format: 'cjs' },
),
}),
],
base: './',
build: {
outDir: '../../dist/renderer',
emptyOutDir: true,
sourcemap: true,
},
resolve: {
alias: {
'@': join(__dirname, 'src'),
},
},
server: {
host: pkg.env.VITE_DEV_SERVER_HOST,
port: pkg.env.VITE_DEV_SERVER_PORT,
},
})
/**
* For usage of Electron and NodeJS APIs in the Renderer process
* @see https://github.com/caoxiemeihao/electron-vue-vite/issues/52
*/
export function electron(
entries: Parameters<typeof optimizer>[0] = {}
): Plugin {
const builtins = builtinModules.filter((t) => !t.startsWith('_'))
/**
* @see https://github.com/caoxiemeihao/vite-plugins/tree/main/packages/resolve#readme
*/
return optimizer({
electron: electronExport(),
...builtinModulesExport(builtins),
...entries,
})
function electronExport() {
return `
/**
* For all exported modules see https://www.electronjs.org/docs/latest/api/clipboard -> Renderer Process Modules
*/
const electron = require("electron");
const {
clipboard,
nativeImage,
shell,
contextBridge,
crashReporter,
ipcRenderer,
webFrame,
desktopCapturer,
deprecate,
} = electron;
export {
electron as default,
clipboard,
nativeImage,
shell,
contextBridge,
crashReporter,
ipcRenderer,
webFrame,
desktopCapturer,
deprecate,
}
`
}
function builtinModulesExport(modules: string[]) {
return modules
.map((moduleId) => {
const nodeModule = require(moduleId)
const requireModule = `const M = require("${moduleId}");`
const exportDefault = `export default M;`
const exportMembers =
Object.keys(nodeModule)
.map((attr) => `export const ${attr} = M.${attr}`)
.join(';\n') + ';'
const nodeModuleCode = `
${requireModule}
${exportDefault}
${exportMembers}
`
return { [moduleId]: nodeModuleCode }
})
.reduce((memo, item) => Object.assign(memo, item), {})
}
}

View File

@ -1,9 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["packages/renderer/src/*"]
}
}
}

View File

Before

Width:  |  Height:  |  Size: 9.6 MiB

After

Width:  |  Height:  |  Size: 9.6 MiB

View File

Before

Width:  |  Height:  |  Size: 3.4 MiB

After

Width:  |  Height:  |  Size: 3.4 MiB

View File

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,5 +0,0 @@
import { build } from 'vite'
await build({ configFile: 'packages/main/vite.config.ts' })
await build({ configFile: 'packages/preload/vite.config.ts' })
await build({ configFile: 'packages/renderer/vite.config.ts' })

View File

@ -1,70 +0,0 @@
import { spawn } from 'child_process'
import { createServer, build } from 'vite'
import electron from 'electron'
const query = new URLSearchParams(import.meta.url.split('?')[1])
const debug = query.has('debug')
/**
* @type {(server: import('vite').ViteDevServer) => Promise<import('rollup').RollupWatcher>}
*/
function watchMain(server) {
/**
* @type {import('child_process').ChildProcessWithoutNullStreams | null}
*/
let electronProcess = null
const address = server.httpServer.address()
const env = Object.assign(process.env, {
VITE_DEV_SERVER_HOST: address.address,
VITE_DEV_SERVER_PORT: address.port,
})
/**
* @type {import('vite').Plugin}
*/
const startElectron = {
name: 'electron-main-watcher',
writeBundle() {
if (electronProcess) {
electronProcess.removeAllListeners()
electronProcess.kill()
}
electronProcess = spawn(electron, ['.'], { stdio: 'inherit', env })
electronProcess.once('exit', process.exit)
},
}
return build({
configFile: 'packages/main/vite.config.ts',
mode: 'development',
plugins: [!debug && startElectron].filter(Boolean),
build: {
watch: {},
},
})
}
/**
* @type {(server: import('vite').ViteDevServer) => Promise<import('rollup').RollupWatcher>}
*/
function watchPreload(server) {
return build({
configFile: 'packages/preload/vite.config.ts',
mode: 'development',
plugins: [{
name: 'electron-preload-watcher',
writeBundle() {
server.ws.send({ type: 'full-reload' })
},
}],
build: {
watch: {},
},
})
}
// bootstrap
const server = await createServer({ configFile: 'packages/renderer/vite.config.ts' })
await server.listen()
await watchPreload(server)
await watchMain(server)

View File

@ -1,10 +1,10 @@
import electron from '@/assets/electron.png'
import react from '@/assets/react.svg'
import vite from '@/assets/vite.svg'
import styles from '@/styles/app.module.scss'
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 = () => {
const App: React.FC = () => {
const [count, setCount] = useState(0)
return (
@ -60,8 +60,8 @@ const App = () => {
</a>
<div className={styles.staticPublic}>
Place static files into the{' '}
<code>src/renderer/public</code> folder
<img style={{ width: 90 }} src="./images/node.png" />
<code>/public</code> folder
<img style={{ width: 77 }} src="./node.png" />
</div>
</div>
</header>

View File

@ -1,9 +1,8 @@
export { }
declare global {
interface Window {
// Expose some Api through preload script
// Expose API through preload script
fs: typeof import('fs')
ipcRenderer: import('electron').IpcRenderer
removeLoading: () => void

12
src/main.tsx Normal file
View File

@ -0,0 +1,12 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import 'styles/index.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
)
window.removeLoading()

View File

@ -1,15 +1,27 @@
{
"extends": "./paths.json",
"compilerOptions": {
"baseUrl": ".",
"target": "ESNext",
"module": "ESNext",
"allowJs": true,
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"paths": {
"@/*": ["src/*"],
"styles/*": ["src/assets/styles/*"]
},
"allowJs": false,
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"esModuleInterop": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"strict": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
}
}
},
"include": ["src", "types.d.ts"],
"references": [{ "path": "./tsconfig.node.json" }]
}

8
tsconfig.node.json Normal file
View File

@ -0,0 +1,8 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node"
},
"include": ["vite.config.ts"]
}

1
types.d.ts vendored
View File

@ -1,4 +1,3 @@
declare namespace NodeJS {
interface ProcessEnv {
NODE_ENV: 'development' | 'production'

52
vite.config.ts Normal file
View File

@ -0,0 +1,52 @@
import { rmSync } from 'fs'
import { join } from 'path'
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import electron from 'vite-plugin-electron'
import renderer from 'vite-plugin-electron/renderer'
import pkg from './package.json'
rmSync(join(__dirname, 'dist'), { recursive: true, force: true }) // v14.14.0
// https://vitejs.dev/config/
export default defineConfig({
resolve: {
alias: {
'@': join(__dirname, 'src'),
'styles': join(__dirname, 'src/assets/styles'),
},
},
plugins: [
react(),
electron({
main: {
entry: 'electron/main/index.ts',
vite: {
build: {
sourcemap: false,
outDir: 'dist/electron/main',
},
},
},
preload: {
input: {
// You can configure multiple preload scripts here
splash: join(__dirname, 'electron/preload/splash.ts'),
},
vite: {
build: {
// For debug
sourcemap: 'inline',
outDir: 'dist/electron/preload',
}
},
},
}),
// Enables use of Node.js API in the Renderer-process
renderer(),
],
server: {
host: pkg.env.VITE_DEV_SERVER_HOST,
port: pkg.env.VITE_DEV_SERVER_PORT,
},
})