Electron 自定义协议、资源加载与深链接

理解开发和打包后的资源加载差异,了解什么时候需要用链接直接打开应用中的指定内容。

🎯 引言

开发时页面通常来自 http://localhost,打包后则来自本地文件。路径变化是 Electron 初学者经常遇到的问题。本篇先掌握判断加载方式的方法,再认识自定义协议的用途。


🧱 开发和生产的加载差异

electron/main.js
const isDevelopment = Boolean(process.env.VITE_DEV_SERVER_URL);

if (isDevelopment) {
    win.loadURL(process.env.VITE_DEV_SERVER_URL);
} else {
    win.loadFile(path.join(__dirname, '../dist/index.html'));
}

开发环境需要服务器提供热更新,生产环境则需要把构建目录一起放进应用包。不要把开发服务器地址写死成生产环境配置。


🧰 为什么要使用自定义协议

大多数 Electron 项目不需要自定义协议,使用 loadFile() 加载 index.html 就可以了。

只有当应用有很多内部页面、图片和脚本,或者希望所有内部资源都使用统一地址时,自定义协议才有价值。它的作用可以简单理解为:给应用内部资源起一个统一的地址,并由主进程决定这个地址能打开什么文件。

例如,应用不直接使用某个具体的本地文件路径,而是使用:

app://local/index.html

主进程收到这个地址后,再把它对应到应用包里的 dist/index.html

electron/main.js
const { app, BrowserWindow, net, protocol } = require('electron/main');
const path = require('node:path');
const { pathToFileURL } = require('node:url');

app.whenReady().then(async () => {
    protocol.handle('app', async (request) => {
        const url = new URL(request.url);

        if (url.pathname !== '/index.html') {
            return new Response('Not found', { status: 404 });
        }

        const filePath = path.join(__dirname, '../dist/index.html');
        return net.fetch(pathToFileURL(filePath).toString());
    });

    const win = new BrowserWindow({
        width: 960,
        height: 640,
    });

    await win.loadURL('app://local/index.html');
});

这段代码的过程是:

  1. 窗口请求 app://local/index.html
  2. protocol.handle('app', ...) 接收到这个请求。
  3. 主进程只允许访问 index.html,并把它映射到 dist/index.html
  4. 找不到的路径返回 404,不会读取任意本地文件。

初学项目先使用 loadFile() 即可。只有确实需要统一管理内部资源时,再考虑自定义协议;它不是必须配置。


🔗 深链接有什么用

应用内部的普通链接,例如 Vue Router 的 /project/123,直接在当前窗口跳转就可以了,不需要深链接。

深链接解决的是另一个问题:用户在应用外部点击一个链接时,能不能打开桌面应用,并直接定位到应用里的指定内容。

这个链接可能来自浏览器地址栏、邮件、系统通知或其他应用。例如:

electron-course://project/123

用户点击这个链接后,操作系统会寻找注册了 electron-course 协议的 Electron 应用,启动应用,并把 project/123 交给它处理。应用收到后,就可以打开项目 123

深链接适合这些场景:

  • 邮件中有项目链接,点击后直接打开桌面应用里的项目。
  • 浏览器页面提供“在桌面应用中打开”按钮。
  • 系统通知中带有任务链接,点击后直接定位到某条任务。

如果你的应用只处理窗口内部的普通页面跳转,不需要从浏览器、邮件或通知打开指定内容,就不需要实现深链接。

主进程拿到深链接后,通常只做三件事:解析链接、检查参数、通知当前窗口显示对应内容。

electron/main.js
function handleDeepLink(rawUrl) {
    const url = new URL(rawUrl);

    if (url.protocol !== 'electron-course:') {
        return;
    }

    const projectId = url.pathname.slice(1);

    if (!/^\d+$/.test(projectId)) {
        return;
    }

    mainWindow.webContents.send('project:open', projectId);
}

electron-course: 是应用自己注册的协议,project/123 表示要打开的内容。示例中的正则检查是为了避免把任意文字都当成项目编号。

真正接入时,还需要把这个协议注册到 Windows、macOS 或 Linux,并结合单实例处理。这个过程属于发布配置;初学阶段先理解“链接如何告诉应用要打开什么内容”即可。


🧾 小节总结

  • 开发环境通常加载 Vite URL,生产环境通常加载本地构建文件。
  • 打包后必须检查资源是否被复制到应用包。
  • 自定义协议适合统一管理应用内部资源。
  • 深链接可以把外部地址转换成应用内页面。
  • 本地路径和 URL 参数都需要限制和校验。

❓ 知识问答

Q1:所有 Electron 项目都需要自定义协议吗?

A:不需要。简单项目使用 loadFile 就够了,自定义协议适合有统一资源加载或深链接需求的项目。

Q2:为什么开发环境能打开,打包后却白屏?

A:常见原因是生产路径错误,或者构建资源没有被复制进应用包。

🧪 小练习

在下面的代码中补充生产环境页面路径:

win.loadFile(
    // 请在这里编写代码
);

🎉 现在你已经理解了 Electron 资源加载中最容易混淆的开发与生产差异。