一、什么是electron Electron 是一个开源的框架,用于构建跨平台的桌面应用程序。它允许开发者使用常见的 Web 技术,如 HTML、CSS 和 JavaScript,来构建原生级别的桌面应用程序,而无需学习额外的桌面开发语言或工具。
二、electron特性 跨平台性 Electron 允许开发者使用一套代码构建同时运行在多个平台上的桌面应用程序,包括 Windows、macOS 和 Linux。这种跨平台性使得开发者能够在不同操作系统下开发和部署应用程序,大大提高了开发效率和应用程序的可用性。
基于 Chromium 和 Node.js Electron 是基于开源的 Chromium 浏览器引擎和 Node.js 运行时环境构建的。它利用了这两个强大的平台,使得开发者可以在应用程序中使用最新的 Web 技术,并且可以轻松地访问本地系统资源和操作系统功能。
使用 Web 技术 开发 Electron 应用程序与开发网页应用程序非常相似,开发者可以使用熟悉的 Web 技术,如 HTML、CSS 和 JavaScript,来构建桌面应用程序的用户界面和交互逻辑。此外,开发者还可以使用流行的 Web 框架和库,如 React、Vue.js 和 Angular 等,来构建复杂的用户界面。
原生级别的体验 虽然 Electron 应用程序是使用 Web 技术构建的,但它们提供了与原生桌面应用程序相似的用户体验和性能。开发者可以利用 Electron 提供的丰富的 API 来访问本地系统资源和操作系统功能,例如文件系统、窗口管理、系统通知、系统菜单等,从而为用户提供原生级别的体验。
社区和生态系统 Electron 拥有一个庞大的开发者社区和丰富的生态系统,提供了大量的第三方库、工具和插件,以帮助开发者构建和部署高质量的桌面应用程序。开发者可以从社区中获取支持和资源,解决开发中遇到的问题,并且分享经验和技术。
三、新建一个项目 使用Electron Forge
首先,您需要全局安装 Electron Forge,这样您就可以在命令行中使用它的命令行工具
1 npm install -g @electron-forge/cli
在命令行中,进入您的项目目录,然后运行以下命令来初始化一个新的 Electron 项目
1 electron-forge init my -electron-app
进入项目目录,并安装依赖
1 2 cd my-electron-app npm install
使用以下命令在开发模式下运行应用程序
当您准备好发布应用程序时,可以使用 Electron Forge 打包应用程序。运行以下命令
四、核心 api app 模块 app 模块是 Electron 应用程序的入口点,负责控制应用程序的生命周期和事件处理。 可以使用 app 模块来监听应用程序的各种事件,如 ready、window-all-closed、activate 等。 还可以使用 app 模块来设置应用程序的基本信息,如应用程序名称、图标、版本号等。
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 const { app, ipcMain, dialog, Notification } = require ("electron" ); const { createMenu } = require ("./menu" ); const { createWindow } = require ("./window" ); const { createTray } = require ("./tray" ); let mainWindow = null ; const handleMainMessage = async (event, data ) => { const notification = new Notification ({ title : "Jeffrey的城堡" , body : data, }); notification.show (); const result = await dialog.showOpenDialog ({ title : "选择文件" , defaultPath : app.getPath ("home" ), filters : [ { name : "Text Files" , extensions : ["txt" ] }, { name : "All Files" , extensions : ["*" ] }, ], properties : ["openFile" ], }); if (!result.canceled ) { const filePath = result.filePaths [0 ]; mainWindow.webContents .send ("file-selected" , filePath); } return '网页,你的来信我收到了! 签名壳' ; };const handleSecondInstance = ( ) => { if (mainWindow) { if (mainWindow.isMinimized ()) { mainWindow.restore (); } else { mainWindow.focus (); } } }; app.on ("second-instance" , handleSecondInstance); app.on ("ready" , () => { ipcMain.handle ("main-message" , handleMainMessage); mainWindow = createWindow (); createMenu (mainWindow); createTray (mainWindow); }); app.on ("window-all-closed" , () => { if (process.platform !== "darwin" ) { app.quit (); } }); app.on ("activate" , () => { if (!mainWindow) { mainWindow = createWindow (); } });
这段代码是一个 Electron 主进程的脚本,负责处理应用程序的启动、窗口创建、消息通信以及对话框的显示。其中包括了创建主窗口实例、处理主进程接收到的消息、监听窗口关闭和激活事件等功能。
BrowserWindow 模块 BrowserWindow 模块用于创建和控制应用程序的窗口。 可以使用 BrowserWindow 类创建新的窗口,并设置窗口的大小、位置、标题、图标等属性。 还可以使用 BrowserWindow 类来加载 Web 内容,如 HTML 文件、URL 或者远程网页。
window.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 const { BrowserWindow , screen } = require ("electron" ); const path = require ("path" ); const { getIsQuitting } = require ("./quitting" ); const createWindow = ( ) => { const { width, height } = screen.getPrimaryDisplay ().workAreaSize ; const windowWidth = Math .floor (width * 0.8 ); const windowHeight = Math .floor (height * 0.8 ); const win = new BrowserWindow ({ width : windowWidth, height : windowHeight, webPreferences : { nodeIntegration : true , preload : path.join (__dirname, 'preload.js' ) }, icon : path.join (__dirname, "icon.png" ), }); win.loadURL ("http://localhost:8080/" ); win.webContents .on ("did-finish-load" , () => { win.setTitle ("Jeffrey的魔法城堡" ); }); win.on ("close" , (event ) => { if (!getIsQuitting ()) { event.preventDefault (); win.hide (); } }); return win; };module .exports = { createWindow };
于创建一个 Electron 窗口,并设置窗口的大小、图标、加载页面的 URL,以及监听窗口关闭事件并做出相应处理,同时注入了一个预加载脚本preload.js。
ipcMain 和 ipcRenderer 模块 ipcMain 和 ipcRenderer 模块用于在主进程和渲染进程之间进行通信。 主进程可以使用 ipcMain 模块监听来自渲染进程的事件,并发送消息给渲染进程。 渲染进程可以使用 ipcRenderer 模块发送消息给主进程,并监听来自主进程的事件。
使用preload.js
preload.js 是 Electron 中用于在渲染进程(网页)加载之前执行的脚本。它的作用是在渲染进程中注入一些预定义的变量、函数或者模块,以便在渲染进程中直接使用这些变量、函数或者模块,而不需要在渲染进程中再次引入。
preload.js
1 2 3 4 5 6 7 8 9 const { contextBridge, ipcRenderer } = require('electron' ); contextBridge.exposeInMainWorld('electronSDK' , { upload: async (data) => { return await ipcRenderer.invoke('main-message' , data); }, });
这段代码是在渲染进程中使用 Electron 的 contextBridge 模块,将主进程中的 ipcRenderer 对象暴露给渲染进程,以便渲染进程可以安全地与主进程进行通信。通过 contextBridge.exposeInMainWorld 方法,将名为 electronSDK 的对象暴露给渲染进程的全局作用域,其中包含了一个名为 upload 的函数。这个函数用于向主进程发送消息,并等待主进程返回结果。在内部,它调用了 ipcRenderer.invoke 方法,向主进程发送名为 main-message 的事件,并传递数据 data,然后返回事件处理结果。
网页端
1 2 3 4 5 window.electronSDK .upload('您好,壳! 签名网页' ) .then((res ) => { console .log (res) })
这段代码是在渲染进程中调用 Electron 主进程中预加载的名为 electronSDK 的对象的 upload 方法。该方法用于向主进程传递消息,并期望返回一个 Promise 对象。当主进程处理完消息后,会将结果返回给渲染进程,并在 Promise 对象的 then 方法中获取到返回的结果 res,然后进行相应的处理,例如打印日志。
Menu 和 MenuItem 模块用于创建和管理应用程序的菜单和上下文菜单。 可以使用 Menu 类创建菜单栏、应用程序菜单和上下文菜单,并添加菜单项和子菜单。 MenuItem 类表示菜单项,可以设置菜单项的标签、图标、快捷键等属性。
menu.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 const { Menu , app, BrowserWindow } = require ("electron" );const { setIsQuitting } = require ("./quitting" ); const createMenu = ( ) => { const menuTemplate = [ { label : "应用" , submenu : [ { label : "刷新" , accelerator : "CmdOrCtrl+R" , click : () => { BrowserWindow .getFocusedWindow ().reload (); }, }, { type : "separator" }, { label : "退出" , accelerator : "CmdOrCtrl+Q" , click : () => { setIsQuitting (true ); app.quit (); }, }, ], }, ]; const menu = Menu .buildFromTemplate (menuTemplate); Menu .setApplicationMenu (menu); };module .exports = { createMenu };
这段代码用于创建 Electron 应用程序的菜单,其中包含了刷新和退出两个功能,分别对应快捷键 “CmdOrCtrl+R” 和 “CmdOrCtrl+Q”,并在退出时设置了一个退出标志。
dialog 模块 dialog 模块用于显示系统对话框,如打开文件对话框、保存文件对话框、警告框、错误框等。 可以使用 dialog 模块打开系统对话框,并获取用户的输入或选择。
临时存放在index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const result = await dialog.showOpenDialog ({ title : "选择文件" , defaultPath : app.getPath ("home" ), // 设置默认打开路径为用户的主目录 filters : [ { name : "Text Files" , extensions : ["txt" ] }, // 只允许选择文本文件 { name : "All Files" , extensions : ["*" ] }, // 允许选择所有文件 ], properties : ["openFile" ], // 只允许选择文件,不允许选择文件夹 });if (!result.canceled) { const filePath = result.filePaths[0 ]; mainWindow.webContents.send ("file-selected" , filePath); }return '网页,你的来信我收到了! 签名壳' ;
网页端
1 2 3 4 5 window .electronSDK .on ("file-selected" , (filePath ) => { console .log ("文件路径" + filePath); });
shell 模块 shell 模块用于执行一些系统级别的操作,如打开外部链接、打开文件、拷贝文件等。 可以使用 shell 模块打开外部链接、文件、文件夹,并执行一些系统默认的操作。
1 2 3 4 const { shell, dialog } = require ('electron' ); shell.openExternal ('https://www.example.com' );
1 2 3 4 5 6 7 8 9 10 11 12 13 dialog.showOpenDialog ({ properties : ['openFile' ] }) .then (result => { if (!result.canceled ) { const filePath = result.filePaths [0 ]; } }) .catch (err => { console .error (err); });const fs = require ('fs' );
1 2 3 4 5 6 7 8 9 10 11 const sourcePath = 'path/to/source/file.txt' ;const destinationPath = 'path/to/destination/file.txt' ; fs.copyFile (sourcePath, destinationPath, (err ) => { if (err) { console .error ('拷贝文件时出错:' , err); return ; } console .log ('文件已成功拷贝' ); });
五、完整案例 目录结构
src
icon.png
index.js
menu.js
preload.js
quitting.js
tray.js
window.js
forge.config.js
前面示例中,已经提供了index.js、menu.js、preload.js、window.js文件,这里仅补充其他文件
quitting.js 1 2 3 4 5 6 7 8 9 10 11 12 13 // quitting.js let isQuitting = false ;function setIsQuitting (value) { is Quitting = value; }function getIsQuitting () { return isQuitting ; } module.exports = { setIsQuitting, getIsQuitting };
tray.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 const { app, Menu , Tray } = require ("electron" );const path = require ("path" );const { setIsQuitting } = require ("./quitting" );let tray = null ;function createTray (window ) { tray = new Tray (path.join (__dirname, "icon.png" )); const contextMenu = Menu .buildFromTemplate ([ { label : "退出" , type : "normal" , click : () => { setIsQuitting (true ); app.quit (); }, }, ]); tray.setToolTip ("Jeffrey的魔法城堡" ); tray.setContextMenu (contextMenu); tray.on ("double-click" , () => { window .show (); }); }module .exports = { createTray };
forge.config.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 const { FusesPlugin } = require('@electron-forge/plugin-fuses' ); const { FuseV1Options, FuseVersion } = require('@electron/fuses' ); module.exports = { packagerConfig: { asar: true , icon: 'src/icon.png' , platform: 'win32' , arch: 'x64' , }, rebuildConfig: {}, makers: [ { name: '@electron-forge/maker-squirrel' , config: {}, }, { name: '@electron-forge/maker-zip' , platforms: ['darwin' , 'win32' ], } ], plugins: [ { name: '@electron-forge/plugin-auto-unpack-natives' , config: {}, }, new FusesPlugin({ version: FuseVersion.V1, [FuseV1Options.RunAsNode ]: false , [FuseV1Options.EnableCookieEncryption ]: true , [FuseV1Options.EnableNodeOptionsEnvironmentVariable ]: false , [FuseV1Options.EnableNodeCliInspectArguments ]: false , [FuseV1Options.EnableEmbeddedAsarIntegrityValidation ]: true , [FuseV1Options.OnlyLoadAppFromAsar ]: true , }), ], };
六、功能演示 网页与壳通信
使用托盘
使用安装包