# Electron

基于 Chromium 和 Node.js,使用 JavaScript、HTML 和 CSS 构建跨平台桌面应用程序的框架。

# hello world

mkdir hello-world
cd ./hello-world
npm init
npm install --save-dev electron
touch main.js
(将package.json 的入口文件改为main.js,添加start命令: electron .)

编写...完成后使用 Electron Forge 打包和分发
npm install --save-dev @electron-forge/cli
npx electron-forge import
npm run make

Electron-forge 会创建 out 文件夹,包括了可运行程序

# 使用 Electron fiddle

Electron fiddle 更像一个 code pan,不需要安装任何依赖,只关注编写几则代码就能运行起来,后面也可以 export 完整的程序代码 或 publish 一个 "fiddle" 到 github。

# 原理

# 技术架构

  • Chromium
  • NodeJS
  • NativeAPI(MacOS、Win、Linux)
  • 主进程(Main Process):泛指 main.js,一个应用仅有一个主进程,只有它可以调用 Native apis。
  • 渲染进程(Renderer Process):一个应用可有多个渲染进程,它完成 windows 中界面的渲染。
  • IPC:渲染进程与主进程间通信手段。

# LS 通信

# 生命周期

  • ready: app 初始化完成 - app - 1

  • dom-ready:一个窗口中的文本加载完成 - window.webContents - 2

  • did-finsh-load:导航完成时触发 - window.webContents - 3

  • window-all-closed: 所有窗口都被关闭时触发 - app - 4

  • before-quit: 在关闭窗口之前触发 - app - 5

  • will-quit:在窗口关闭并且应用退出时触发 - app - 6

  • quit: 当所有窗口被关闭时触发 - app - 7

  • closed: 当窗口关闭时触发,此时应删除窗口引用 - win - 8

注意:如果监听了 window-all-closed,需要主动调用 app.quit() 才会执行 quit 相关的事件。

# 流程模型

# API 摘要

  • BrowserWindow 应用窗口
new BrowserWindow({
  x: 100,
  y: 100, //设置窗口显示的位置,相对于当前屏幕的左上角// 默认情况下创建一个窗口对象之后就会显示
  show: false, // 设置为false 就不会显示了,可以在 ready-to-show 事件调用 win.show()
  width: 800,
  height: 400,
  minHeight: 200,
  minWidth: 300, // 可以通过 min max 来设置当前应用窗口的最大和最小尺寸
  resizable: false, // 是否允许缩放应用的窗口大小
  icon: "",
  title: "",
  frame: false, // 取消显示选项卡菜单
  autoHideMenuBar: true, // frame 为 ture 时可用来隐藏菜单条
  webPreferences: {
    nodeIntegration: true, // 可以在渲染进程中使用 require()
    enableRemoteModule: true, // 可以在渲染进程中使用 remote 调用主进程API
  },
  parent: obj, // 指定父子窗口
  modal: true, // 子窗口是否为模态窗口
});
  • electron.remote 在渲染进程中是无法调用某些主进程中的 API 的,如 BrowserWindow。 需要开启 webPreferences.remote,并这样调用:
new remote.BrowserWindow();
  • Menu 自定义菜单栏
const menuTemp = [
  {
    label: "角色",
    submenu: [
      { label: "复制" role:'copy'},
      { label: "剪切" role:'cut'},
      { label: "粘贴" role:'paste'},
      { label: "最小化" role:'minimize'}
    ],
  },
  {
    label: '类型',
    submenu: [
      {label: '选项1', type: 'checkbox'},
      {type: 'separator'}
      {label: '选项2', type: 'radio'},
      {label: 'windows', type: 'submenu', role: 'windowMenu'}
    ]
  },
  {
    label: '其他',
    submenu: [
      {
        label: '打开',
        icon: './icon.png',
        accelerator: 'ctrl + o',
        click(){
          console.log('open');
        }
      }
    ]
  }
];

const menu = Menu.buildFromTemplate(menuTemp);
Menu.setApplicationMenu(menu);

// 动态创建菜单
const mi =  new MenuItem({label: '自定义', type: 'normal'});
const subMi = new MenuItem({label: '自定义', type: 'normal'});
mi.append(subMi);
menu.append(mi);
Menu.setApplicationMenu(menu);

// 右键菜单(在渲染进程中)
window.addEventListener('contextmenu',(ev)=>{
  ev.preventDefault();
  menu.popup({
    window: remote.getCurrentWindow()
  })
})
  • ipcMain、ipcRenderer 异步通信
// 渲染进程中
ipcRenderer.send("msg1", "i am Renderer");
ipcRenderer.on("response1", (ev, data) => {
  console.log(data); // copy that
});

// 主进程中
ipcMain.on("msg1", (ev, data) => {
  console.log(data); // i am Renderer
  ev.sender.send("response1", "copy that.");
});

同步通信

// 渲染进程中
const val = ipcRenderer.sendSync("msg1", "i am Renderer");
console.log(val); // copy that

// 主进程中
ipcMain.on("msg1", (ev, data) => {
  console.log(data); // i am Renderer
  ev.returnValue = "copy that";
});
  • Dialog
// 文件弹框
remote.dialog.showOpenDialog({
  defaultPath: __dirname,
  buttonLabel: "请选择",
  title: "xxx",
  properties: ["openFile", "multiSelections"],
  filters: [
    {
      name: "代码文件",
      extensions: ["js", "json", "html"],
    },
  ],
});

// 错误弹框
remote.dialog.showErrorBox("标题", "内容");
  • shell、iframe

// 在浏览器中打开链接
shell.openExternal('https://www.baidu.com');

// 在应用中打开链接
<iframe>
  • Notification
// 消息通知
window.Notification(title, option)
  • globalShortcut
// 注册快捷键
globalShortcut.register('ctrl + q');
globalShortcut.isRegisted('ctrl + q');

// 注销快捷键
globalShortcut.unregister('ctrl + q');
globalShortcut.unregisterAll();
  • clipboard
var cpd = null;
// 复制文本
cpd = clipboard.writeText(str);

// 黏贴文本
clipboard.readText(cpd);

// 复制图片
clipboard.writeImage(nativeImage.createFromPath('./temp.png'));

// 黏贴图片
clipboard.readImage()

# 开发

  1. 使用 nodemon 监听文件并自动执行。 package.json add
"scripts":{
 "start" :"nodemon --watch main.js --exec npm run build",
 "build":"electron"
}
  1. 使用 ctrl + R,来刷新界面

  2. 窗口中使用 ctrl + shift + i,打开调试面板

  3. 默认打开窗口的调试面板 mainWin.webContents.openDevTools()

  4. 工具包

electron-is-dev:声明开发或生产环境变量
concurrently:连接多个命令,中间使用空格分隔,转义"
wait-on:等待某个结果执行之后再去执行后续的命令
cross-env:阻止调起浏览器,跨平台环境变量设置