mirror of
https://github.com/electron-vite/electron-vite-vue
synced 2025-01-19 20:07:06 +08:00
Merge pull request #72 from caoxiemeihao/nodeIntegration
feat(nodeIntegration): set nodeIntegration as true by default
This commit is contained in:
commit
8f2d9f5c48
35
README.md
35
README.md
@ -61,13 +61,32 @@ A `dist` folder will be generated everytime when `dev` or `build` command is exe
|
|||||||
├
|
├
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `dependencies` vs `devDependencies`
|
||||||
|
|
||||||
|
- First, you need to know if the package is still needed at runtime after packed.
|
||||||
|
|
||||||
|
- Packages like [serialport](https://www.npmjs.com/package/serialport), [sqlite3](https://www.npmjs.com/package/sqlite3) are node-native modules and should be placed in `dependencies`. Vite will not build them and will treat them as externals.
|
||||||
|
|
||||||
|
- Packages like [vue](https://www.npmjs.com/package/vue), [react](https://www.npmjs.com/package/react) are pure javascript modules and can be built with Vite. They can be listed in `devDependencies` which helps reducing the size of bundled product.
|
||||||
|
|
||||||
## Use Electron, NodeJs API
|
## Use Electron, NodeJs API
|
||||||
|
|
||||||
> 🚧 By default, using Electron or NodeJS API in the rederer process is strongly discouraged. For anyone who needs to bypass the security constraints, take a look at this template 👉 **[electron-vite-boilerplate](https://github.com/caoxiemeihao/electron-vite-boilerplate)**
|
> 🚧 Due to [electron security](https://www.electronjs.org/docs/latest/tutorial/security/) constraints, using Electron or NodeJS API in the rederer process is strongly discouraged.
|
||||||
|
|
||||||
As electron suggested, if you need access to the Electron and NodeJS API in the renderer process, you need to create a context bridge and expose the APIs you need to the renderer process.
|
The template provides two methods for using the NodeJs API in the rendering process:
|
||||||
|
|
||||||
Note that if your project uses typescript, you also need to add type declarations to the `Window` interface.
|
1. Bypass the security constraints (**default**), located in the [main](https://github.com/caoxiemeihao/electron-vue-vite/tree/main) branch. `nodeIntegration` is enabled by default, making it easy to use.:tada:, but there are certain security risks 🚧.
|
||||||
|
2. Inject Render by preload script, located in the [withoutNodeIntegration](https://github.com/caoxiemeihao/electron-vue-vite/tree/withoutNodeIntegration) branch. `nodeIntegration` is turned off by default, the official recommended way of electron, more secure:lock:.
|
||||||
|
|
||||||
|
|
||||||
|
**For [1](https://github.com/caoxiemeihao/electron-vue-vite/tree/main), all NodeJs and Electron APIs can be used directly in the rendering process.**
|
||||||
|
|
||||||
|
|
||||||
|
**For [2](https://github.com/caoxiemeihao/electron-vue-vite/tree/withoutNodeIntegration), all NodeJs, Electron APIs injected into the rendering process via `Preload-script`**
|
||||||
|
|
||||||
|
you need to create a context bridge and expose the APIs you need to the renderer process.
|
||||||
|
|
||||||
|
Note that if your project uses typescript, you also need to add type declarations to the `Window` interface, for example:
|
||||||
|
|
||||||
* **packages/preload/index.ts**
|
* **packages/preload/index.ts**
|
||||||
|
|
||||||
@ -98,6 +117,8 @@ Note that if your project uses typescript, you also need to add type declaration
|
|||||||
console.log('ipcRenderer', window.ipcRenderer)
|
console.log('ipcRenderer', window.ipcRenderer)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Finally, either way, for third-party NodeJs APIs (e.g. `sqlite3`), You'll also need to declare how it was imported in `packages/renderer/vite.config.ts` `defineConfig.plugins` so that the template can recognize them correctly. 👉 reference `issues` [resolveElectron](https://github.com/caoxiemeihao/electron-vue-vite/issues/52)
|
||||||
|
|
||||||
## Use SerialPort, SQLite3 or other node-native addons in Main-process
|
## Use SerialPort, SQLite3 or other node-native addons in Main-process
|
||||||
|
|
||||||
- First, you need to make sure the packages are listed in the "dependencies" since they are still needed at runtime after the project is packed.
|
- First, you need to make sure the packages are listed in the "dependencies" since they are still needed at runtime after the project is packed.
|
||||||
@ -126,14 +147,6 @@ export default {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## `dependencies` vs `devDependencies`
|
|
||||||
|
|
||||||
- First, you need to know if the package is still needed at runtime after packed.
|
|
||||||
|
|
||||||
- Packages like [serialport](https://www.npmjs.com/package/serialport), [sqlite3](https://www.npmjs.com/package/sqlite3) are node-native modules and should be placed in `dependencies`. Vite will not build them and will treat them as externals.
|
|
||||||
|
|
||||||
- Packages like [vue](https://www.npmjs.com/package/vue), [react](https://www.npmjs.com/package/react) are pure javascript modules and can be built with Vite. They can be listed in `devDependencies` which helps reducing the size of bundled product.
|
|
||||||
|
|
||||||
## Main window
|
## Main window
|
||||||
<img width="400px" src="https://raw.githubusercontent.com/caoxiemeihao/blog/main/electron-vue-vite/screenshot/electron-15.png" />
|
<img width="400px" src="https://raw.githubusercontent.com/caoxiemeihao/blog/main/electron-vue-vite/screenshot/electron-15.png" />
|
||||||
|
|
||||||
|
@ -77,9 +77,20 @@ electron-builder 打包时候会将 dependencies 中的包打包到 app.asar 里
|
|||||||
|
|
||||||
## 渲染进程使用 NodeJs API
|
## 渲染进程使用 NodeJs API
|
||||||
|
|
||||||
> 🚧 因为安全的原因 Electron 默认不支持在 渲染进程 中使用 NodeJs API,但是有些小沙雕就是想这么干,拦都拦不住;实在想那么干的话,用另一个模板更方便 👉 **[electron-vite-boilerplate](https://github.com/caoxiemeihao/electron-vite-boilerplate)**
|
> 🚧 因为 [electron 安全约束的原因](https://www.electronjs.org/docs/latest/tutorial/security/) Electron 默认不支持在 渲染进程 中使用 NodeJs API。
|
||||||
|
|
||||||
**推荐所有的 NodeJs、Electron API 通过 `Preload-script` 注入到 渲染进程中,例如:**
|
在渲染进程中使用 NodeJs API 的方式,本模版提供了两种方案:
|
||||||
|
|
||||||
|
1. 忽视安全约束(**默认**),位于[main](https://github.com/caoxiemeihao/electron-vue-vite/tree/main) 分支。默认开启了 `nodeIntegration`,开箱即用使用简便:tada:,但是有一定 XSS 攻击风险 🚧。
|
||||||
|
|
||||||
|
2. 通过 preload 方式向 Render 注入,位于 [withoutNodeIntegration](https://github.com/caoxiemeihao/electron-vue-vite/tree/withoutNodeIntegration) 分支。默认关闭了 `nodeIntegration`,electron 官方推荐的方式,更加安全:lock:。
|
||||||
|
|
||||||
|
**对于[方案 1](https://github.com/caoxiemeihao/electron-vue-vite/tree/main),所有的 NodeJs、Electron API 可以直接在 渲染进程 中使用。**
|
||||||
|
|
||||||
|
|
||||||
|
**对于[方案 2](https://github.com/caoxiemeihao/electron-vue-vite/tree/withoutNodeIntegration),所有的 NodeJs、Electron API 通过 `Preload-script` 注入到 渲染进程中**
|
||||||
|
|
||||||
|
您需要创建一个 context bridge,并向渲染进程暴露所需的 API。请注意,如果您的项目使用 typescript,则还需要将类型声明添加到 `Window` interface,例如:
|
||||||
|
|
||||||
* **packages/preload/index.ts**
|
* **packages/preload/index.ts**
|
||||||
|
|
||||||
@ -116,19 +127,37 @@ electron-builder 打包时候会将 dependencies 中的包打包到 app.asar 里
|
|||||||
console.log('ipcRenderer', window.ipcRenderer)
|
console.log('ipcRenderer', window.ipcRenderer)
|
||||||
```
|
```
|
||||||
|
|
||||||
**如果你真的在这个模板中开启了 `nodeIntegration: true` `contextIsolation: false` 我不拦着
|
最后,不管是哪种方式,对于第三方 NodeJs API (例如 `sqlite3`) 你还需要在 `packages/renderer/vite.config.ts` 的 `defineConfig.plugins` 中声明它的导入方式,从而让模版能够正确识别它们。关于原理 `resolveElectron` **最好了解下**
|
||||||
🚧 但是要提醒你做两件事儿**
|
|
||||||
|
|
||||||
1. `preload/index.ts` 中的 `exposeInMainWorld` 删掉,已经没有用了
|
|
||||||
|
|
||||||
```diff
|
|
||||||
- contextBridge.exposeInMainWorld('fs', fs)
|
|
||||||
- contextBridge.exposeInMainWorld('ipcRenderer', ipcRenderer)
|
|
||||||
```
|
|
||||||
|
|
||||||
2. `configs/vite-renderer.config` 中有个 `resolveElectron` **最好了解下**
|
|
||||||
👉 这里有个 `issues` [请教一下vite-renderer.config中的resolveElectron函数](https://github.com/caoxiemeihao/electron-vue-vite/issues/52)
|
👉 这里有个 `issues` [请教一下vite-renderer.config中的resolveElectron函数](https://github.com/caoxiemeihao/electron-vue-vite/issues/52)
|
||||||
|
|
||||||
|
## 在主进程中使用 SerialPort,SQLite3 等 node-native addons
|
||||||
|
|
||||||
|
- 首先,您需要确保这些第三方 node-native addons 被放到了 "dependencies" 中,以二进制文件确保能够被打包。
|
||||||
|
|
||||||
|
- main 进程和 preload 脚本也需要对应在 vite [build.lib](https://vitejs.dev/config/#build-lib) 中配置打包,需要配置 rollup 选项。
|
||||||
|
|
||||||
|
**查看更多:** 👉 [packages/main/vite.config.ts](https://github.com/caoxiemeihao/electron-vue-vite/blob/main/packages/main/vite.config.ts)
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
build: {
|
||||||
|
// built lib for Main-process, Preload-script
|
||||||
|
lib: {
|
||||||
|
entry: 'index.ts',
|
||||||
|
formats: ['cjs'],
|
||||||
|
fileName: () => '[name].js',
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
// configuration here
|
||||||
|
external: [
|
||||||
|
'serialport',
|
||||||
|
'sqlite3',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## 运行效果
|
## 运行效果
|
||||||
<img width="400px" src="https://raw.githubusercontent.com/caoxiemeihao/blog/main/electron-vue-vite/screenshot/electron-15.png" />
|
<img width="400px" src="https://raw.githubusercontent.com/caoxiemeihao/blog/main/electron-vue-vite/screenshot/electron-15.png" />
|
||||||
|
|
||||||
|
1768
package-lock.json
generated
1768
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -19,6 +19,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.19.2",
|
"@playwright/test": "^1.19.2",
|
||||||
|
"@types/sqlite3": "^3.1.8",
|
||||||
"@vitejs/plugin-vue": "^2.1.0",
|
"@vitejs/plugin-vue": "^2.1.0",
|
||||||
"electron": "16.0.8",
|
"electron": "16.0.8",
|
||||||
"electron-builder": "^22.14.5",
|
"electron-builder": "^22.14.5",
|
||||||
@ -26,7 +27,7 @@
|
|||||||
"simple-git-hooks": "^2.7.0",
|
"simple-git-hooks": "^2.7.0",
|
||||||
"typescript": "^4.5.5",
|
"typescript": "^4.5.5",
|
||||||
"vite": "^2.7.13",
|
"vite": "^2.7.13",
|
||||||
"vite-plugin-resolve": "^1.6.1",
|
"vite-plugin-resolve": "^1.8.0",
|
||||||
"vue": "^3.2.29",
|
"vue": "^3.2.29",
|
||||||
"vue-tsc": "^0.31.1"
|
"vue-tsc": "^0.31.1"
|
||||||
},
|
},
|
||||||
@ -39,5 +40,8 @@
|
|||||||
"electron",
|
"electron",
|
||||||
"vue3",
|
"vue3",
|
||||||
"rollup"
|
"rollup"
|
||||||
]
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"sqlite3": "^5.0.2"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ if (!app.requestSingleInstanceLock()) {
|
|||||||
app.quit()
|
app.quit()
|
||||||
process.exit(0)
|
process.exit(0)
|
||||||
}
|
}
|
||||||
|
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'
|
||||||
|
|
||||||
let win: BrowserWindow | null = null
|
let win: BrowserWindow | null = null
|
||||||
|
|
||||||
@ -19,11 +20,14 @@ async function createWindow() {
|
|||||||
win = new BrowserWindow({
|
win = new BrowserWindow({
|
||||||
title: 'Main window',
|
title: 'Main window',
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: join(__dirname, '../preload/index.cjs')
|
preload: join(__dirname, '../preload/index.cjs'),
|
||||||
|
nodeIntegration: true,
|
||||||
|
contextIsolation: false,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if (app.isPackaged || process.env["DEBUG"]) {
|
if (app.isPackaged || process.env['DEBUG']) {
|
||||||
|
// Load built files in the debug mode instead of reading from vite server
|
||||||
win.loadFile(join(__dirname, '../renderer/index.html'))
|
win.loadFile(join(__dirname, '../renderer/index.html'))
|
||||||
} else {
|
} else {
|
||||||
// 🚧 Use ['ENV_NAME'] avoid vite:define plugin
|
// 🚧 Use ['ENV_NAME'] avoid vite:define plugin
|
||||||
@ -33,9 +37,20 @@ async function createWindow() {
|
|||||||
win.webContents.openDevTools()
|
win.webContents.openDevTools()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Communicate with the Renderer-process.
|
||||||
|
win.webContents.on('ipc-message', (_, channel, ...args) => {
|
||||||
|
switch (channel) {
|
||||||
|
case 'app.getPath':
|
||||||
|
win?.webContents.send('app.getPath', app.getPath(args[0]))
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Test active push message to Renderer-process
|
// Test active push message to Renderer-process
|
||||||
win.webContents.on('did-finish-load', () => {
|
win.webContents.on('did-finish-load', () => {
|
||||||
win?.webContents.send('main-process-message', (new Date).toLocaleString())
|
win?.webContents.send('main-process-message', new Date().toLocaleString())
|
||||||
})
|
})
|
||||||
|
|
||||||
// Make all links open with the browser, not with the application
|
// Make all links open with the browser, not with the application
|
||||||
|
@ -1,36 +1,11 @@
|
|||||||
import fs from 'fs'
|
|
||||||
import { contextBridge, ipcRenderer } from 'electron'
|
|
||||||
import { domReady } from './utils'
|
import { domReady } from './utils'
|
||||||
import { useLoading } from './loading'
|
import { useLoading } from './loading'
|
||||||
|
|
||||||
const { appendLoading, removeLoading } = useLoading()
|
const { appendLoading, removeLoading } = useLoading()
|
||||||
|
window.removeLoading = removeLoading;
|
||||||
|
|
||||||
;(async () => {
|
;(async () => {
|
||||||
await domReady()
|
await domReady()
|
||||||
|
|
||||||
appendLoading()
|
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
|
|
||||||
}
|
|
||||||
|
3
packages/renderer/src/global.d.ts
vendored
3
packages/renderer/src/global.d.ts
vendored
@ -3,9 +3,6 @@ export { }
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
// Expose some Api through preload script
|
|
||||||
fs: typeof import('fs')
|
|
||||||
ipcRenderer: import('electron').IpcRenderer
|
|
||||||
removeLoading: () => void
|
removeLoading: () => void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
import ipcRendererSample from './mainModules/ipcRendererSample'
|
||||||
|
import fsExample from './mainModules/builtinModuleSample'
|
||||||
|
import sqliteExample from './mainModules/nodeModulesSample'
|
||||||
|
|
||||||
createApp(App)
|
createApp(App)
|
||||||
.mount('#app')
|
.mount('#app')
|
||||||
.$nextTick(window.removeLoading)
|
.$nextTick(() => {
|
||||||
|
window.removeLoading()
|
||||||
// console.log('fs', window.fs)
|
ipcRendererSample()
|
||||||
// console.log('ipcRenderer', window.ipcRenderer)
|
fsExample()
|
||||||
|
sqliteExample()
|
||||||
// Usage of ipcRenderer.on
|
})
|
||||||
window.ipcRenderer.on('main-process-message', (_event, ...args) => {
|
|
||||||
console.log('[Receive Main-process message]:', ...args)
|
|
||||||
})
|
|
||||||
|
13
packages/renderer/src/mainModules/builtinModuleSample.ts
Normal file
13
packages/renderer/src/mainModules/builtinModuleSample.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
|
const fsExample = () => {
|
||||||
|
fs.lstat(process.cwd(),(err,stats)=>{
|
||||||
|
if(err){
|
||||||
|
console.log(err)
|
||||||
|
}else{
|
||||||
|
console.log(stats);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export default fsExample
|
9
packages/renderer/src/mainModules/ipcRendererSample.ts
Normal file
9
packages/renderer/src/mainModules/ipcRendererSample.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import {ipcRenderer} from 'electron' // rename from cjs to esm by plugin `resolveElectron` in packages/renderer/vite.config.ts
|
||||||
|
|
||||||
|
const ipcRendererHelloWorld = () => {
|
||||||
|
// Usage of ipcRenderer.on
|
||||||
|
ipcRenderer.on('main-process-message', (_event, ...args) => {
|
||||||
|
console.log('[Receive Main-process message]:', ...args)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export default ipcRendererHelloWorld
|
31
packages/renderer/src/mainModules/nodeModulesSample.ts
Normal file
31
packages/renderer/src/mainModules/nodeModulesSample.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import path from 'path'
|
||||||
|
import { ipcRenderer } from 'electron'
|
||||||
|
import sqlite3 from 'sqlite3'
|
||||||
|
|
||||||
|
const createSqlite3 = (userDataPath: string): Promise<sqlite3.Database> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const dbpath = path.join(userDataPath, 'sqlite3.db')
|
||||||
|
console.log(dbpath)
|
||||||
|
const db = new sqlite3.Database(dbpath, (error) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resolve(db)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const sqliteExample = () => {
|
||||||
|
ipcRenderer.on('app.getPath', async (_, userDataPath) => {
|
||||||
|
try {
|
||||||
|
const db = await createSqlite3(userDataPath)
|
||||||
|
console.log(db)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
ipcRenderer.send('app.getPath', 'userData')
|
||||||
|
|
||||||
|
}
|
||||||
|
export default sqliteExample
|
@ -20,6 +20,9 @@ export default defineConfig({
|
|||||||
* 'electron-store': 'const Store = require("electron-store"); export default Store;',
|
* 'electron-store': 'const Store = require("electron-store"); export default Store;',
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
|
{
|
||||||
|
sqlite3: 'const sqlite3 = require("sqlite3"); export default sqlite3;',
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
base: './',
|
base: './',
|
||||||
@ -40,7 +43,6 @@ export function resolveElectron(
|
|||||||
resolves: Parameters<typeof resolve>[0] = {}
|
resolves: Parameters<typeof resolve>[0] = {}
|
||||||
): Plugin {
|
): Plugin {
|
||||||
const builtins = builtinModules.filter((t) => !t.startsWith('_'))
|
const builtins = builtinModules.filter((t) => !t.startsWith('_'))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see https://github.com/caoxiemeihao/vite-plugins/tree/main/packages/resolve#readme
|
* @see https://github.com/caoxiemeihao/vite-plugins/tree/main/packages/resolve#readme
|
||||||
*/
|
*/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user