对于可以读取本地文件的桌面程序来说,通过关联文件的方式启动也是常见的功能,比如本地的播放器或各种编辑器,可以在资源管理器双击对应的文件来启动程序。

在 Windows 中,可以通过写注册表的方式来关联文件启动,下面关联 .abc 后缀的文件,双击 .abc 后缀的文件就启动程序和读取文件。

因为我使用的是 Windows,我这里只测试了 Windows,Linux 和 macOS 不一定适用!

初始化一个 Electron 程序

创建一个存放项目的目录,在项目目录打开命令行,输入:

npm init -y

Electron 需要打包成 Windows 安装包才能关联文件,在开发模式直接运行和打包为免安装的程序都无法关联。

下面安装 electron 和 electron-builder:

npm install electron electron-builder --save-dev

安装完成后在 package.jsonscripts 中加入运行和打包命令:

{
  "scripts": {
    "start": "electron .",
    "pack": "electron-builder --dir",
    "dist": "electron-builder"
  }
}

完成基本的窗口界面

在项目跟目录创建一个 main.jsindex.js 作为主进程的入口文件,然后创建一个窗口:

const {app, BrowserWindow} = require('electron');
const fs = require('fs');

let mainWindow  = null;

app.on('ready', async () => {
  // 创建窗口
  mainWindow = new BrowserWindow({
    width: 800,
    height: 500,
    webPreferences: {
      webSecurity: false,
      contextIsolation: true,
      preload: path.join(__dirname, 'preload.js')
    }
  });

  // 加载html
  await mainWindow.loadFile(path.join(__dirname, 'assets', 'index.html'));

  // 窗口关闭
  mainWindow.on('close', () => {
    mainWindow = null;
  });
});

因为安全原因,现在的 Electron 已经无法在渲染进程中引入 Electron 和 Node 模块使用。

官方推荐的方式是主进程通过 preload 把 ipc 通信相关的功能暴露给渲染进程,渲染进程如果需要操作 Electron 或 Node 就通过 ipc 和主进程通信,由主进程来调用 Electron 和 Node API。

我上面设置的 preload 配置文件在项目根目录下的 preload.js,下面在根目录创建一个 preload.js,把 ipc 的部分功能暴露给渲染进程:

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

contextBridge.exposeInMainWorld('electronAPI', {
  onResponse: (channel, listener) => {
    ipcRenderer.on(channel, listener);
  },
  'ipc-invoke': (channel, listener) => {
    ipcRenderer.invoke(channel, listener);
  }
});

上面的 Electron API 会被挂载到 window.electronAPI

我这里会把前端的 HTML CSS JS 放到项目目录下的 assets 目录,下面在 assets 目录下创建 index.html,加入基本的 HTML 和一个 textarea

<div id="box">
  <textarea id="text-box"></textarea>
</div>

这个 textarea 用来显示打开的文件。

完成后可以尝试输入:

npm run start

启动 Electron。

关联文件

下面关联 .abc 后缀的文件,双击 .abc 的文件就启动程序然后读取文件显示。

上面在入口文件已经完成了窗口的创建,下面还是在 index.js 入口文件读取文件:

const {app, BrowserWindow, dialog} = require('electron');
const path = require('path');
const fs = require('fs');

let filePathToOpen = null;  // 存储文件路径

// 判断一下是否包括命令参数和程序是否打包
if (process.argv.length >= 2 && app.isPackaged) {
  // 获取打开的文件路径
  const openFilePath  = process.argv[1];
  if (openFilePath && path.isAbsolute(openFilePath)) {
    filePathToOpen = openFilePath;
  }
}

let mainWindow  = null;

app.on('ready', async () => {
  // 创建窗口
  mainWindow = new BrowserWindow({
    width: 800,
    height: 500,
    webPreferences: {
      webSecurity: false,
      contextIsolation: true,
      preload: path.join(__dirname, 'preload.js')
    }
  });

  // 加载html
  await mainWindow.loadFile(path.join(__dirname, 'assets', 'index.html'));

  // 如果有文件路径就读取文件
  if (filePathToOpen) {
    fs.readFile(filePathToOpen, 'utf-8', (error, data) => {
      // 出错就弹窗显示错误信息
      if (error) {
        dialog.showMessageBoxSync(mainWindow, {
          type: 'error',
          title: '读取文件出错',
          message: `无法读取文件:${filePathToOpen}`,
          detail: error.message,
          buttons: ['关闭'],
          defaultId: 0,
          noLink: true
        });
        return false;
      }

      // 成功读取文件内容就把文件内容发送到渲染进程
      mainWindow.webContents.send('file-content', data);
    });
  }

  // 窗口关闭
  mainWindow.on('close', () => {
    mainWindow = null;
  });
});

在 Windows 中,从关联后缀的文件打开时,可以通过 process.argv[1] 获取文件的位置,比如关联了 .abc 后缀的文件,双击 .abc 文件启动程序时,就可以通过 process.argv[1] 获取该 .abc 文件的位置。

我上面使用的 fs.readFile 是完全读取整个文件,对于小文件可以用这种方式,但是对于一些大文件可能就需要部分读取。

创建一个 JS 文件,在前端的 HTML 中引入,用于显示主进程传过来的内容:

const textBox = document.querySelector('#text-box');

// 监听处理主进程传过来的内容
window.electronAPI.onResponse('file-content', (ev, result) => {
  // 在 textarea 中显示打开的文件
  textBox.value = result;
});

上面使用 document.querySelector('#text-box') 获取的就是前端 HTML 中的 textarea

打包

打包是关联文件中比较重要的部分,只有打包了才能关联文件启动。

electron-builder 的打包需要在 package.json 中的 build 中配置,下面是一个包含关联文件的 Windows 打包配置:

{
  "build": {
    "appId": "electron-open-file",
    "productName": "electron-open-file",
    "icon": "assets/favicon.ico",
    "copyright": "Copyright © 2024",
    "compression": "maximum",
    "asar": true,
    "win": {
      "icon": "assets/logo.ico",
      "target": "nsis",
      "legalTrademarks": "changbin1997"
    },
    "nsis": {
      "oneClick": false,
      "perMachine": true,
      "allowToChangeInstallationDirectory": true,
      "createDesktopShortcut": true,
      "createStartMenuShortcut": false
    },
    "directories": {
      "output": "release"
    },
    "files": [
      "index.js",
      "preload.js",
      "assets/**/*",
      "package.json",
      "!**/node_modules/*",
      "!node_modules/**/node_modules/*",
      "!package-lock.json",
      "!screenshots/**/*",
      "!**/.*",
      "!**/*.map",
      "!**/test?(s)/**/*",
      "!**/*.{md,txt,log,o,hprof,orig,pyc,pyo,rbc,pdb,ilk,bak}",
      "!**/._*",
      "!README.md",
      "!LICENSE"
    ],
    "fileAssociations": [
      {
        "ext": "abc",
        "name": "abc file",
        "description": "点击打开 .abc 文件",
        "icon": "assets/logo.ico"
      }
    ]
  }
}

上面把 Electron 程序打包为 Windows 安装包,打包完成后会在项目目录下的 release 输出打包的程序。

下面是详细的配置说明:

appIdproductName 是程序 ID 和程序名称。

icon 是默认的程序图标,我的所有图标文件都存放在 assets 目录。

compression 设置打包的压缩率,我这里的 maximum 是高压缩,打包的时间会稍长一些,打包后的程序也要更小。

asar 设置把资源文件打包到 .asar 文件中,可以减少打包后的程序目录中的文件数量。

win 是 Windows 相关的打包配置。

win.icon 程序图标,最好使用 256*256 的 .ico 图标。

win.target 设置打包目标,我设置的 nsis 是打包为 .exe 的安装包,你也可以打包成 .msi 的安装包。

win.legalTrademarks 是商标信息。

nsis 是安装包配置。

nsis.oneClick 是否使用一键安装,我这里没有使用一键安装,可以自己选择安装目录。

nsis.perMachine 是为所有用户安装,如果设置为 false 就是为当前用户安装,程序会默认安装到当前用户的用户目录。

nsis.allowToChangeInstallationDirectory 允许更改默认的安装位置。

nsis.createDesktopShortcut 安装完成后创建桌面快捷方式。

nsis.createStartMenuShortcut 安装完成后创建开始菜单快捷方式。

directories.output 是设置打包完成后的输出位置。

files 可以设置要打包的文件和要排除的文件,我这里打包的文件包括:

  • index.js Electron 入口文件
  • package.json
  • preload.js 用于暴露 Electron 和 Node API 给渲染进程
  • assets 目录中的所有文件,也就是前端渲染进程使用的文件

其它包含 ! 都是不用打包的文件。

fileAssociations 是配置文件关联,需要传入一个数组,一个程序可以关联多个后缀的文件。

fileAssociations.ext 要关联的文件后缀。

fileAssociations.name 用于文件属性和气球提示显示的文件类型名称。

fileAssociations.description 用于气球提示和文件属性显示的文件描述。

fileAssociations.icon 关联文件使用的图标。

我上面关联了 .abc 后缀的文件,程序安装后,所有 .abc 后缀的文件都会显示我设置的 logo.ico 图标。

初始化项目的时候,在 scripts 中加入了 packdist 两条打包相关的命令,其中:

npm run pack

可以把程序打包为一个目录,目录中有可以直接运行的免安装 exe 程序。

使用:

npm run dist

可以打包为 Windows 安装包。

打包的时候你需要有一个比较好的国际互联网连接,如果你无法访问 Github 之类的网站,可能无法成功打包,如果网速较慢打包的速度也会比较慢。

考虑到随着 electron 和 electron-builder 的版本更新,一些 API 和配置可能会改变,下面是我这里演示使用的完整 package.json

{
  "name": "electron-open-file",
  "version": "1.0.0",
  "description": "点击打开 .abc 文件",
  "main": "index.js",
  "scripts": {
    "start": "electron .",
    "pack": "electron-builder --dir",
    "dist": "electron-builder"
  },
  "keywords": [],
  "author": "changbin1997",
  "license": "ISC",
  "devDependencies": {
    "electron": "^37.2.6",
    "electron-builder": "^26.0.12"
  },
  "build": {
    "appId": "electron-open-file",
    "productName": "electron-open-file",
    "icon": "assets/favicon.ico",
    "copyright": "Copyright © 2024",
    "compression": "maximum",
    "asar": true,
    "win": {
      "icon": "assets/logo.ico",
      "target": "nsis",
      "legalTrademarks": "changbin1997"
    },
    "nsis": {
      "oneClick": false,
      "perMachine": true,
      "allowToChangeInstallationDirectory": true,
      "createDesktopShortcut": true,
      "createStartMenuShortcut": false
    },
    "directories": {
      "output": "release"
    },
    "files": [
      "index.js",
      "preload.js",
      "assets/**/*",
      "package.json",
      "!**/node_modules/*",
      "!node_modules/**/node_modules/*",
      "!package-lock.json",
      "!screenshots/**/*",
      "!**/.*",
      "!**/*.map",
      "!**/test?(s)/**/*",
      "!**/*.{md,txt,log,o,hprof,orig,pyc,pyo,rbc,pdb,ilk,bak}",
      "!**/._*",
      "!README.md",
      "!LICENSE"
    ],
    "fileAssociations": [
      {
        "ext": "abc",
        "name": "abc file",
        "description": "点击打开 .abc 文件",
        "icon": "assets/logo.ico"
      }
    ]
  }
}

devDependencies 中有 electron 和 electron-builder 的版本信息。

查看最终效果

打包完成后把打包的程序安装到电脑上,创建一个 .abc 后缀的文件,如果配置没有错误的话,应该可以看到 .abc 后缀的文件图标就是关联文件配置的图标。

下面使用自带的 Windows 记事本打开 .abc 文件,添加一些内容:

通过Windows记事本编辑.abc后缀的文件

可以查看一下 .abc 文件的文件属性:

查看.abc文件的文件属性

在打开方式中可以看到上面配置的 electron-open-file。

双击 .abc 文件就会启动关联的 Electron 程序,然后读取文件显示:

启动关联的Electron程序并读取文件显示

我在前端 HTML 文件中用了一个 textarea 来显示读取的文件内容。

当你卸载了程序后,关联后缀的文件图标也会变为默认图标。