Nodejs环境

 
https://nodejs.org/en/download/

https://nodejs.org/dist/latest-v20.x/node-v20.18.2-x64.msi

    

nvm安装

 
https://github.com/coreybutler/nvm-windows/releases


nvm install lts
nvm list 

C:\Users\itoracle_17068343192>nvm use 22.13.1
Now using node v22.13.1 (64-bit)

C:\Users\itoracle_17068343192>node -v
v22.13.1

C:\Users\itoracle_17068343192>npm -v
10.9.2

将npm的版本降到8.5.5这个解决兼容python,让node可以兼容python3
npm install -g npm@8.5.5

npm install -g yarn
    

 
安装指定版本 
nvm install v20.9.0

    

 
二进制版本 
mkdir -p ~/wks/nodejs/app 
cd ~/wks/nodejs/app/
wget https://nodejs.org/dist/v20.9.0/node-v20.9.0-linux-x64.tar.xz
tar -xvf node-v20.9.0-linux-x64.tar.xz
cd node-v20.9.0-linux-x64/

https://nodejs.org/en/download/

环境变量

 
export NODEWKS=/home/xt/wks/nodejs
export NODE_BASE=$NODEWKS/app/node-v20.9.0-linux-x64/
export PATH=$NODE_PATH/.bin:$NODE_BASE/bin:$PATH
export NODE_GLOBAL=$NODEWKS/app/global
export PATH=$NODE_GLOBAL/bin:$PATH

下面的设置会替代NODE_PATH的作用
mkdir -p $NODEWKS/app/{global,cache}
npm config set prefix "$NODEWKS/app/global"
npm config set cache  "$NODEWKS/app/cache"

NODE_GLOBAL就是上面global的路径,将全局安装的包加到PATH路径中

 
which node
/home/xt/wks/nodejs/app/node-v20.9.0-linux-x64/bin/node

$ npm -v
10.1.0

将npm的版本降到8.5.5这个解决兼容python,让node可以兼容python3
npm install -g npm@8.5.5

全局安装会安装到global目录 

yarn

 
npm install -g yarn

下面的根据需要安装,不需要就不用设置
yarn config set -g registry http://mirrors.cloud.tencent.com/npm/
yarn config set -g sass_binary_site http://mirrors.cloud.tencent.com/npm/node-sass/


which yarn
/home/xt/wks/nodejs/app/global/bin/yarn

yarn常用命令

 
安装包:
yarn install // 安装package.json里所有包,并将包即它所有依赖项保存进yarn.lock
yarn install --flat  //安装一个包的单一版本
yarn install --force //强制重新下载所有包
yarn install --production //只安装dependencies里的包
yarn install --no-lockfile  //不读取或生成yarn.lock
yarn install --pure-lockfile //不生成yarn.lock

添加包(会更新package.json 和 yarn.lock):
yarn add [package]  //在当前项目中添加一个依赖包,会自动更新到package.json 和 yarn.lock文件中
yarn add [package]@[version]  //安装指定版本,这里指的是主要版本,如果需要精确到小版本,使用 -E参数
yarn add [package]@[tag] //安装某个tag (比如beta,next或者latest)

不指定依赖类型默认安装到dependencies里,你可以指定依赖类型:
yarn  add --dev/-D   //加到devDependencies
yarn  add --peer/-P  //加到peerDependencies
yarn  add --optional /-O //加到optionalDependencies

默认安装包的主要版本里的最新版本,下面两个命令可以指定版本:
yarn add  --exact /-E  //安装包的精确版本。例如: yarn add foo@1.2.3会接受1.9.1版,但是yarn add foo@1.2.3 --exact直接说1.2.3版
yarn add  --title /-T //安装包的次要版本里的最新版。例如:yarn add foo@1.2.3 --title 会接受1.2.9,但不接受1.3.0

 
yarn install 
npm run start

 

    

 


 

  

 


Electron项目搭建

 
https://www.electronjs.org/zh/docs/latest/tutorial/quick-start

mkdir qsbiji && cd qsbiji
yarn init
yarn add --dev electron

最后,您希望能够执行 Electron 如下所示,
在您的 package.json配置文件中的scripts字段下增加一条start命令:

    
{
    "scripts": {
      "start": "electron ."
    }
  }


yarn start 

 

    

ubuntu electron-packager打包

 

npm install --save-dev electron-packager

./node_modules/.bin/electron-packager . 73biji --platform=linux --arch=x64

vscode 环境中可以运行,出了该环境无法在ubuntu桌面运行 

 
xt@ai:~/wks$ cd /home/xt/wks/nodejs/qsbiji/73biji-linux-x64
xt@ai:~/wks/nodejs/qsbiji/73biji-linux-x64$ ls
73biji                  chrome_crashpad_handler  libEGL.so     libvk_swiftshader.so  LICENSES.chromium.html  resources.pak            version
chrome_100_percent.pak  chrome-sandbox           libffmpeg.so  libvulkan.so.1        locales                 snapshot_blob.bin        vk_swiftshader_icd.json
chrome_200_percent.pak  icudtl.dat               libGLESv2.so  LICENSE               resources               v8_context_snapshot.bin
xt@ai:~/wks/nodejs/qsbiji/73biji-linux-x64$ ./73biji 

 
https://webpack.js.org/
  

windows打包

 
electron-squirrel-startup是windows平台上的依赖包

首先要创建nodejs项目并init 
注意Init时,描述,作者等提示写的内容不能为空 

init完毕后,初始化一个简单的项目,配置index.js,index.html,
可以run之后,再进行下面的打包

npm install --save-dev @electron-forge/cli
npx electron-forge import
npm run make 
    

 
打包demo:soft/windows/kaifa/73biji.rar 


 


 


 


 
https://www.electronforge.io/cli#make

npm install --save-dev @electron-forge/cli

npx create-electron-app@latest my-app

 
https://www.electronforge.io/

npx create-electron-app@latest my-app

npx create-electron-app@latest 73biji --template=webpack

cd my-app
npm install --save-dev @electron-forge/cli
npx electron-forge init --template=webpack

如果有yarn.lock文件要先删除这个文件 
npx electron-forge import
npm start

npm install --save-dev @electron-forge/publisher-github

maker配置

 
// If your config is only in package.json:
// Only showing the relevant configuration for brevity
{
    "config": {
    "forge": {
        "makers": [
        {
            "name": "@electron-forge/maker-zip",
            "platforms": ["darwin", "linux"], // optional
            "config": {
                // Config here
            }
        }
        ]
    }
    }
}
    
npm run make

 
    npx electron-forge import

    https://www.electronforge.io/cli#package

    npx electron-forge package --arch="x64"  --platform="linux"
    

 
    
npm install --save-dev @electron-forge/cli
npx electron-forge init --template=webpack
npx electron-forge import

    

 


基本架构

 
https://www.electronjs.org/zh/docs/latest/tutorial/tutorial-prerequisites
    

Electron中的 ES 模块 (ESM)
https://www.electronjs.org/zh/docs/latest/tutorial/esm


使用预加载脚本来增强渲染器:Electron modules及Polyfilled 的全局模块

 

BrowserWindow 的预加载脚本运行在具有 HTML DOM 和 Node.js、Electron API 的有限子集访问权限的环境中。

::: info 预加载脚本沙盒化

从 Electron 20 开始,预加载脚本默认 沙盒化 ,不再拥有完整 Node.js 环境的访问权。 
实际上,这意味着你只拥有一个 polyfilled 的 require 函数,这个函数只能访问一组有限的 API。

    
app:控制应用程序的事件生命周期。
https://www.electronjs.org/zh/docs/latest/api/app


process
https://www.electronjs.org/zh/docs/latest/api/process


buffer
https://nodejs.org/api/buffer.html

clearImmediate
https://nodejs.org/api/timers.html#timers_clearimmediate_immediate


setImmediate
https://nodejs.org/api/timers.html#timers_setimmediate_callback_args

    

NodeJs

 

events
https://nodejs.org/api/events.html

timers
https://nodejs.org/api/timers.html

url
https://nodejs.org/api/url.html
    

webPreferences加载JS

 

// Create the browser window.
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {//为了将脚本附在渲染进程上,在 BrowserWindow 构造器中使用 webPreferences.preload 传入脚本的路径。
    preload: path.join(__dirname, 'preload.js')
}
})
    
为了将脚本附在渲染进程上,在 BrowserWindow 构造器中使用 webPreferences.preload 传入脚本的路径。

nodejs/73biji/preload.js中配置全局变量

 

const { contextBridge } = require('electron')

contextBridge.exposeInMainWorld('versions', {
  node: () => process.versions.node,
  chrome: () => process.versions.chrome,
  electron: () => process.versions.electron
  // 除函数之外,我们也可以暴露变量
})

之后可以在页面或页面中的JS直接使用

 
const information = document.getElementById('info')
information.innerText = `本应用正在使用 Chrome (v${versions.chrome()}), Node.js (v${versions.node()}), 和 Electron (v${versions.electron()})`

    

 


 


 
渲染:就是Html加载展示的过程

Electron模块 就是 渲染进程模块,
- 即Html在Electron中渲染
- 多数情况下,html是在浏览器中渲染的,可以将Electron看作一个迷你浏览器 
- 所以Electron的进程也是渲染进程 

主进程 
- 项目是一个Nodejs项目,最初的入口,或者最开始的入口是一个js文件
- 通常是main.js/index.js,这就是主进程 


在主进程中初始化了Electron模型,并将静态资源也传入了进去

 

//__dirname 字符串指向当前正在执行脚本的路径 (在本例中,它指向你的项目的根文件夹)。
//path.join API 将多个路径联结在一起,创建一个跨平台的路径字符串。
const createWindow = () => {
    // Create the browser window.
    const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {//为了将脚本附在渲染进程上,在 BrowserWindow 构造器中使用 webPreferences.preload 传入脚本的路径。
        preload: path.join(__dirname, 'preload.js')
    }
    })

    // 加载 index.html
    mainWindow.loadFile('index.html')

    // 打开开发工具
    // mainWindow.webContents.openDevTools()
}

效率进程

 
Process-specific module aliases (TypeScript)

Electron's npm package also exports subpaths that contain a subset of Electron's TypeScript type definitions.

  electron/main includes types for all main process modules.

  electron/renderer includes types for all renderer process modules.

  electron/common includes types for modules that can run in main and renderer processes.

These aliases have no impact on runtime, but can be used for typechecking and autocomplete.


 
const { app } = require('electron/main')
const { shell } = require('electron/common')

 


 
nodejs项目有一部分是js开发,这部分js在main.js中加载,即在主进程中加载 

现在页面在Electron渲染进程中,
要想在页面中访问main.js主进程中的方法,可以进行以下设置 
  

main.js:引入 ipcMain

 
const { app, BrowserWindow, ipcMain } = require('electron/main')
const path = require('node:path')

const createWindow = () => {
    const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
        preload: path.join(__dirname, 'preload.js')
    }
    })
    win.loadFile('index.html')
}
app.whenReady().then(() => {
    ipcMain.handle('ping', () => 'pong')
    createWindow()
})
  

渲染进程中在preload.js通过ipcRenderer调用main.js主进程的方法

 
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('versions', {
    node: () => process.versions.node,
    chrome: () => process.versions.chrome,
    electron: () => process.versions.electron,
    ping: () => ipcRenderer.invoke('ping')
    // 除函数之外,我们也可以暴露变量
})
  

renderer.js中调用

 
const func = async () => {
    const response = await window.versions.ping()
    console.log(response) // 打印 'pong'
}

func()
  

 

  

window-all-closed事件

 
// quitting the app when no windows are open on non-macOS platforms
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit()
})

app事件

 
The main process also controls your application's lifecycle through Electron's app module. 
This module provides a large set of events and methods that you can use to 
add custom application behavior (for instance, programmatically quitting your application, 
modifying the application dock, or showing an About panel).

其他事件参考
https://www.electronjs.org/zh/docs/latest/api/app

 


 


 


进程间通信

 

https://www.electronjs.org/zh/docs/latest/tutorial/ipc


main.js:主进程通过ipcMain.on定义一个事件

 
ipcMain.on('set-title', (event, title) => {
  const webContents = event.sender
  console.log("webContents")
  console.log(webContents)
  const win = BrowserWindow.fromWebContents(webContents)
  win.setTitle(title)
})

// 加载 index.html
mainWindow.loadFile('index.html')
   
主进程的日志会打印在IDE控制台

渲染器预加载脚本preload.js设置electronAPI.setTitle与主进程中的set-title绑定

 
const { contextBridge, ipcRenderer } = require('electron/renderer')

contextBridge.exposeInMainWorld('electronAPI', {
  setTitle: (title) => ipcRenderer.send('set-title', title)
})
    

渲染器页面调用渲染器的方法

 
const setButton = document.getElementById('btn')
const titleInput = document.getElementById('title')
setButton.addEventListener('click', () => {
  const title = titleInput.value
  window.electronAPI.setTitle(title)
})
    

 

    

 

    

 

    

main.js

 
const { app, BrowserWindow, ipcMain, dialog } = require('electron/main')
const path = require('node:path')

async function handleFileOpen () {
  const { canceled, filePaths } = await dialog.showOpenDialog()
  if (!canceled) {
    return filePaths[0]
  }
}

function createWindow () {
  const mainWindow = new BrowserWindow({
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })
  mainWindow.loadFile('index.html')
}

app.whenReady().then(() => {
  ipcMain.handle('dialog:openFile', handleFileOpen)
  createWindow()
  app.on('activate', function () {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') app.quit()
})

    

 
使用 ipcMain.handle 监听事件
在主进程中,我们将创建一个 handleFileOpen() 函数,
它调用 dialog.showOpenDialog 并返回用户选择的文件路径值。

每当渲染器进程通过 dialog:openFile 通道发送 ipcRender.invoke 消息时,此函数被用作一个回调。 
然后,返回值将作为一个 Promise 返回到最初的 invoke 调用。
    

preload.js

 
const { contextBridge, ipcRenderer } = require('electron/renderer')

contextBridge.exposeInMainWorld('electronAPI', {
  openFile: () => ipcRenderer.invoke('dialog:openFile')
})
    

renderer.js

 
const btn = document.getElementById('btn')
const filePathElement = document.getElementById('filePath')

btn.addEventListener('click', async () => {
  const filePath = await window.electronAPI.openFile()
  filePathElement.innerText = filePath
})
    

 
出于保留历史的目地,会有一些其他的方法。我们建议尽可能使用 ipcRenderer.invoke 。
    

 

    

main.js中添加菜单

 
const menu = Menu.buildFromTemplate([
  {
    label: app.name,
    submenu: [
      {
        click: () => mainWindow.webContents.send('update-counter', 1),
        label: 'Increment'
      },
      {
        click: () => mainWindow.webContents.send('update-counter', -1),
        label: 'Decrement'
      }
    ]
  }
])
Menu.setApplicationMenu(menu)


 
click 处理函数通过 update-counter 通道向渲染器进程发送消息(1 或 -1)。
click: () => mainWindow.webContents.send('update-counter', -1)

preload.js

 
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
  onUpdateCounter: (callback) => ipcRenderer.on('update-counter', (_event, value) => callback(value))
})

renderer.js

 
const counter = document.getElementById('counter')

window.electronAPI.onUpdateCounter((value) => {
  const oldValue = Number(counter.innerText)
  const newValue = oldValue + value
  counter.innerText = newValue.toString()
})


回调

 
从 ipcRenderer.on 回调中将回复发送回主进程。

preload.js

 
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
  onUpdateCounter: (callback) => ipcRenderer.on('update-counter', (_event, value) => callback(value)),
  counterValue: (value) => ipcRenderer.send('counter-value', value)
})

renderer.js

 
const counter = document.getElementById('counter')

window.electronAPI.onUpdateCounter((value) => {
  const oldValue = Number(counter.innerText)
  const newValue = oldValue + value
  counter.innerText = newValue.toString()
  window.electronAPI.counterValue(newValue)
})

main.js

 
// ...
ipcMain.on('counter-value', (_event, value) => {
  console.log(value) // will print value to Node console
})
// ...
  

 

  

 
https://www.electronjs.org/zh/docs/latest/tutorial/message-ports

  

 

  

 

  

 


常用功能

 
Electron 应用程序的结构非常相似。 
作为应用开发者,你将控制两种类型的进程:主进程 和 渲染器进程。 
这类似于上文所述的 Chrome 的浏览器和渲染器进程。

主进程

每个 Electron 应用都有一个单一的主进程,作为应用程序的入口点。 
主进程在 Node.js 环境中运行,这意味着它具有 require 模块和使用所有 Node.js API 的能力。


窗口管理

The main process' primary purpose is to create and
 manage application windows with the BrowserWindow module

BrowserWindow 类的每个实例创建一个应用程序窗口,且在单独的渲染器进程中加载一个网页。 
You can interact with this web content from 
the main process using the window's webContents object.

    

 
const { BrowserWindow } = require('electron')

const win = new BrowserWindow({ width: 800, height: 1500 })
win.loadURL('https://www.bing.com')

const contents = win.webContents
console.log(contents)
    

 
Note: A renderer process is also created for web embeds such as the BrowserView module. 
嵌入式网页内容也可访问 webContents 对象。

由于 BrowserWindow 模块是一个 EventEmitter, 
所以您也可以为各种用户事件 ( 例如,最小化 或 最大化您的窗口 ) 添加处理程序。

当一个 BrowserWindow 实例被销毁时,与其相应的渲染器进程也会被终止。
    

 

    

 


 

  

 


参考