feat(nodeIntegration): set nodeIntegration as true by default

This commit is contained in:
oceanlvr 2022-03-13 21:52:58 +08:00
parent b5bae8a607
commit 42fb116817
12 changed files with 1778 additions and 216 deletions

View File

@ -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
> 🚧 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**
@ -98,6 +117,8 @@ Note that if your project uses typescript, you also need to add type declaration
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
- 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
<img width="400px" src="https://raw.githubusercontent.com/caoxiemeihao/blog/main/electron-vue-vite/screenshot/electron-15.png" />

View File

@ -77,9 +77,20 @@ electron-builder 打包时候会将 dependencies 中的包打包到 app.asar 里
## 渲染进程使用 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**
@ -116,19 +127,37 @@ electron-builder 打包时候会将 dependencies 中的包打包到 app.asar 里
console.log('ipcRenderer', window.ipcRenderer)
```
**如果你真的在这个模板中开启了 `nodeIntegration: true` `contextIsolation: false` 我不拦着
🚧 但是要提醒你做两件事儿**
1. `preload/index.ts` 中的 `exposeInMainWorld` 删掉,已经没有用了
```diff
- contextBridge.exposeInMainWorld('fs', fs)
- contextBridge.exposeInMainWorld('ipcRenderer', ipcRenderer)
```
2. `configs/vite-renderer.config` 中有个 `resolveElectron` **最好了解下**
最后,不管是哪种方式,对于第三方 NodeJs API (例如 `sqlite3`) 你还需要在 `packages/renderer/vite.config.ts``defineConfig.plugins` 中声明它的导入方式,从而让模版能够正确识别它们。关于原理 `resolveElectron` **最好了解下**
👉 这里有个 `issues` [请教一下vite-renderer.config中的resolveElectron函数](https://github.com/caoxiemeihao/electron-vue-vite/issues/52)
## 在主进程中使用 SerialPortSQLite3 等 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" />

1768
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -19,6 +19,7 @@
},
"devDependencies": {
"@playwright/test": "^1.19.2",
"@types/sqlite3": "^3.1.8",
"@vitejs/plugin-vue": "^2.1.0",
"electron": "16.0.8",
"electron-builder": "^22.14.5",
@ -26,7 +27,7 @@
"simple-git-hooks": "^2.7.0",
"typescript": "^4.5.5",
"vite": "^2.7.13",
"vite-plugin-resolve": "^1.6.1",
"vite-plugin-resolve": "^1.8.0",
"vue": "^3.2.29",
"vue-tsc": "^0.31.1"
},
@ -39,5 +40,8 @@
"electron",
"vue3",
"rollup"
]
],
"dependencies": {
"sqlite3": "^5.0.2"
}
}

View File

@ -12,6 +12,7 @@ if (!app.requestSingleInstanceLock()) {
app.quit()
process.exit(0)
}
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'
let win: BrowserWindow | null = null
@ -19,11 +20,14 @@ async function createWindow() {
win = new BrowserWindow({
title: 'Main window',
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'))
} else {
// 🚧 Use ['ENV_NAME'] avoid vite:define plugin
@ -33,9 +37,20 @@ async function createWindow() {
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
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

View File

@ -1,36 +1,11 @@
import fs from 'fs'
import { contextBridge, ipcRenderer } from 'electron'
import { domReady } from './utils'
import { useLoading } from './loading'
const { appendLoading, removeLoading } = useLoading()
window.removeLoading = removeLoading;
;(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

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

View File

@ -1,14 +1,14 @@
import { createApp } from 'vue'
import App from './App.vue'
import ipcRendererSample from './mainModules/ipcRendererSample'
import fsExample from './mainModules/builtinModuleSample'
import sqliteExample from './mainModules/nodeModulesSample'
createApp(App)
.mount('#app')
.$nextTick(window.removeLoading)
// 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)
})
.$nextTick(() => {
window.removeLoading()
ipcRendererSample()
fsExample()
sqliteExample()
})

View 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

View 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

View 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

View File

@ -20,6 +20,9 @@ export default defineConfig({
* 'electron-store': 'const Store = require("electron-store"); export default Store;',
* }
*/
{
sqlite3: 'const sqlite3 = require("sqlite3"); export default sqlite3;',
},
),
],
base: './',
@ -40,7 +43,6 @@ export function resolveElectron(
resolves: Parameters<typeof resolve>[0] = {}
): Plugin {
const builtins = builtinModules.filter((t) => !t.startsWith('_'))
/**
* @see https://github.com/caoxiemeihao/vite-plugins/tree/main/packages/resolve#readme
*/