发布于 2026-01-06 1 阅读
0

使用 Svelte 和 Electron 构建桌面应用程序 DEV 的全球展示挑战赛,由 Mux 呈现:展示你的项目!

使用 Svelte 和 Electron 构建桌面应用程序

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

前几天我看了Felix Riesberg的演讲,他讲到如何使用JavaScript和Electron构建桌面应用程序,这让我很感兴趣,想看看Svelte用起来有多简单。结果发现它真的超级简单,开箱即用!

如果你只想获得一个入门模板来创建你的应用程序,而不想阅读这篇博文,我在这里创建了一个模板。

本文将指导您使用 Svelte 和 Electron 创建一个基本的桌面 Markdown 编辑器。我们将能够编辑 Markdown 文件并查看并排预览,还可以使用 Electron API 和 NodeJS实现打开文件创建新文件保存文件等功能。

你可以在我的博客上看到成品的GIF动画:https://joshuaj.co.uk/building-desktop-app-svelte-electron

我们开始吧!

准备就绪🐣

  1. 克隆 Svelte 模板:npx degit sveltejs/template svelte-markdown-editor

  2. 进入新目录并运行npm install以安装 Svelte 依赖项。

  3. 运行npm install electron marked此命令将安装 Electron,然后安装标记的库,我们将使用该库将 Markdown 转换为 HTML。

  4. 在项目根目录下创建一个main.js文件,这将是我们的 Electron 入口点。

  5. 在 `<script>` 标签内package.json,将启动脚本从 ` "sirv public"<script> ` 更改为"npm run build && electron main.js"`<script>`,这将构建 Svelte 应用程序,然后告诉 Electron 运行它。

  6. 编辑该public/index.html文件,使所有路径.前面都加上 `\n`。例如:`\n`/build/bundle.css变为 ` ./build/bundle.css\n`。否则,Electron 将无法找到这些文件。

  7. 最后,将以下 meta 标签添加到 index.html 文件的 head 部分:<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />。对于这个小型应用程序来说,这不会造成任何问题,但如果没有添加,我们会在控制台中收到警告。点击此处
    了解更多信息。

将 Svelte 与 Electron 连接起来🖇️

现在我们已经完成了样板设置,让我们返回到main.js之前创建的文件,将我们的 Svelte 应用与 Electron 连接起来。

一个基本的 Electron 应用由一个组件app和一个框架组成BrowserWindow,所以让我们导入它们:

const { app, BrowserWindow } = require("electron");
Enter fullscreen mode Exit fullscreen mode

app是我们用来处理应用程序生命周期的组件,而这BrowserWindow是实际渲染 Svelte 应用程序的窗口。

应用程序准备就绪后,首先创建一个新的浏览器窗口;关闭所有窗口后,退出应用程序:

app.whenReady().then(() => {
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true
    }
  });
});

app.on("window-all-closed", () => {
  app.quit();
});
Enter fullscreen mode Exit fullscreen mode

请注意,我们将其设置nodeIntegration为 true,这样我们就可以在 Svelte 文件中使用 NodeJS 代码。

然后,在创建 BrowserWindow 的地方下方,告诉它打开 Svelte 构建时输出的 index.html 文件:

mainWindow.loadFile("./public/index.html");
Enter fullscreen mode Exit fullscreen mode

现在,如果你运行它npm start,应该会打开一个窗口,显示基本的 Hello World Svelte 应用程序。

Svelte 应用的基本图像

创建基础 Markdown 编辑器📝

在实现使用 Electron API 打开、保存和创建新文件的功能之前,让我们先移除 Svelte 样板代码并实现一个基本的 Markdown 编辑器。

在文件中src/main.js,删除 props 参数:

const app = new App({
  target: document.body
});
Enter fullscreen mode Exit fullscreen mode

接下来,创建一个名为:的组件Editor.svelte

<script>
export let markdown = "";
</script>

<style>
    section {
        width: 100%;
        min-height: 95vh;
        border: 0.1rem solid #333;
        flex: 1.5;
    }

    textarea {
        width: 100%;
        height: 100%;
        min-height: 95vh;
        resize: none;
        margin: 0;
        border: none;
    }
</style>

<section>
    <!-- svelte-ignore a11y-autofocus -->
    <textarea bind:value={markdown} autofocus/>
</section>
Enter fullscreen mode Exit fullscreen mode

该组件接收一个名为 `markdown` 的属性markdown,并将其绑定到一个文本区域,以便在文本区域更新时,`markdown` 变量也会自动更新。此外,由于我们的应用程序只有一个输入框,并且它是应用程序的主要焦点,因此我们还会自动聚焦文本区域并移除辅助功能警告。

然后创建一个名为Preview.svelte

<script>
    import marked from "marked";
    export let markdown = "";
</script>

<style>
    section {
        width: 100%;
        min-height: 95vh;
        margin-left: 0.5rem;
        padding: 0.2rem;
        flex: 2;
        background-color: #f5f5f5;
    }

    section :global(*) {
        word-break: break-word;
        white-space: pre-wrap;
    }
</style>

<section>
    {@html marked(markdown)}
</section>
Enter fullscreen mode Exit fullscreen mode

该组件接收一段 Markdown 文本,然后使用marked库将其转换为 HTML。之后,我们使用 Svelte 标签直接将转换后的 HTML 内容输出到页面上{@html}

样式方面并没有什么特别之处,你会注意到我们使用了 `<div>`来定位该部分下的所有内容。这是因为我们想要定位该部分下的所有 HTML,但 Svelte 认为不存在 HTML(因为它是动态生成的),所以如果我们直接使用 `<div>`:global(*)就会收到警告css-unused-selector*

App.svelte文件中将所有内容替换为:

<script>
  import Editor from "./Editor.svelte";
  import Preview from "./Preview.svelte";

  let markdown = "# You can enter markdown here\n\n**This text is bold**\n\n_This text is italic_";

  let activeFilePath;
</script>

<style>
main {
    display: flex;
    flex-direction: column;
}

.file-path {
    font-weight: bold;
}

.editor-and-preview {
    display: flex;
    flex-direction: row;
}
</style>

<main>
    <p class="file-path">
        {activeFilePath ? activeFilePath : "Press 'Save' or hit 'CTRL + S' to save"}
    </p>

    <div class="editor-and-preview">
      <Editor bind:markdown />
      <Preview {markdown} />
    </div>
</main>
Enter fullscreen mode Exit fullscreen mode

这里我们存储一个变量,并告诉 Svelte 将其绑定到组件内部markdown的值,而我们知道该值绑定到文本区域的输入框。因此,我们输入的任何内容都会存储在这个变量中,然后我们将其传递给组件以渲染成 HTML。markdownEditormarkdownPreview

我们还存储了一个activeFilePath变量,稍后我们将使用该变量来显示编辑器中打开的文件。目前,由于没有文件路径,我们只会向用户显示一条消息,告诉他们如何保存。

现在运行程序后,npm start你应该会看到一个可用的 Markdown 编辑器,并带有实时更新的预览功能。

使用 Electron 保存文件💾

现在我们可以编写 Markdown 代码了,让我们给用户提供保存他们所写内容的功能。

App.svelte文件中导入ipcRenderer ,这将允许我们监听来自Electron的消息以及向Electron发送消息:

const { ipcRenderer } = require("electron");
Enter fullscreen mode Exit fullscreen mode

在同一个脚本标签内,我们使用该标签ipcRenderer监听"savefile"消息(消息名称可以是任何你想要的名称),当用户点击菜单项或使用 CTRL + S 时,我们最终会从 Electron 发送此消息。

ipcRenderer.on("savefile", () => {

});
Enter fullscreen mode Exit fullscreen mode

在这个函数内部,我们首先要检查是否存在 activeFilePath,如果存在,则向 Electron 发送一条消息,告诉它将 markdown 变量中的内容保存到 activeFilePath 处的文件中;如果不存在,则发送一条消息,告诉 Electron 询问用户文件应该保存到哪里。

看起来如下:

ipcRenderer.on("savefile", () => {
    if (activeFilePath) {
      ipcRenderer.send("saveexistingfile", {
        path: activeFilePath,
        content: markdown
      });
    } else {
      ipcRenderer.send("savenewfile", markdown);
    }
  });
Enter fullscreen mode Exit fullscreen mode

目前这样做还不会有任何效果,因为我们还没有在主进程(我一直把主进程称为“Electron”)中实现相应的消息监听器和功能。接下来我们就来实现这些功能,首先从保存一个新文件开始。

main.js文件中,从 electron 导入 Menu、MenuItem、ipcMain 和 dialog,并从 nodejs 标准库导入 fs:

const {
  app,
  BrowserWindow,
  Menu,
  MenuItem,
  ipcMain,
  dialog
} = require("electron");

const fs = require("fs");
Enter fullscreen mode Exit fullscreen mode

createNewFile接下来,在回调函数内部创建一个名为 `save_file` 的函数app.whenReady().then,该函数接受一个字符串参数。该函数应该打开一个对话框,要求用户命名文件并选择保存位置,然后将文件保存到该位置,文件名包含提供的字符串。

*相关文档dialog请访问https://www.electronjs.org/docs/api/dialog

回调函数内部app.whenReady().then

const createNewFile = content => {
    dialog.showSaveDialog(mainWindow, {
        title: "Create New File",
        properties: ["showOverwriteConfirmation"],
        filters: [
          {
            name: "Markdown Files",
            extensions: ["md"]
          }
        ]
      })
      .then(({ canceled, filePath }) => {
        if (canceled) return;

        fs.writeFile(filePath, content, err => {
          if (err) return;
        });
      });
  };
Enter fullscreen mode Exit fullscreen mode

在上面的代码中,我们Electron.dialog打开一个保存对话框,该对话框只会保存(和显示)markdown 文件,然后,如果用户没有关闭对话框,我们就使用该fs模块将文件写入他们选择的文件路径。

现在我们有了函数,让我们编写代码,以便在收到"savenewfile"消息时调用它。

在同一位置,编写以下代码:

ipcMain.on("savenewfile", (e, content) => {
    createNewFile(content);
  });
Enter fullscreen mode Exit fullscreen mode

在保存新文件之前,还有最后一步。如果您还记得的话,上面的消息监听器只有在 Svelte 应用发送“savenewfile”消息时才会触发,而“savenewfile”消息只能在 Svelte 应用收到“savefile”消息时发送。因此,当用户从菜单中选择保存或按下 CTRL + S 时,我们必须从 Electron 发送“savefile”消息。

您可以通过在回调函数中创建一个新的菜单app.whenReady().then,然后向其中添加 MenuItem 对象来实现这一点,如下所示:

const menu = new Menu();

menu.append(
    new MenuItem({
      label: "Save",
      accelerator: "CmdOrCtrl+S",
      click: () => mainWindow.webContents.send("savefile")
    })
  );
Enter fullscreen mode Exit fullscreen mode

这里我们说的是,当按下 CTRL + S 或点击标签为“保存”的菜单项时,向 BrowserWindow(我们的 Svelte 应用程序)发送一条名为 的消息"savefile"

最后一点,我们必须将菜单设置为应用程序菜单,如下所示:

Menu.setApplicationMenu(menu);
Enter fullscreen mode Exit fullscreen mode

现在,运行程序npm start,输入一些 Markdown 代码,然后按下 CTRL + S(或菜单项)。你应该会看到一个保存对话框,输入文件名并点击保存后,文件应该就会保存到你选择的目录中。

下一步是添加保存现有文件时的消息监听器。在与上一步相同的位置,添加以下代码:

ipcMain.on("saveexistingfile", (e, { path, content }) => {
    fs.writeFile(path, content, err => {
      if (err) return;
    });
});
Enter fullscreen mode Exit fullscreen mode

这段代码只是使用该fs模块在现有路径创建一个新文件,并将新内容覆盖现有文件。

如果这让你感到困惑,这里有一张图表,展示了渲染进程(我们的 Svelte 应用)和主进程(Electron)之间的消息流转方式:

(在新标签页中打开以全屏查看)
图示渲染器和主程序之间的流程

您也可以在此处阅读更多关于渲染器和主进程的信息。

接下来,我们将添加打开/保存现有文件或使用菜单创建新文件的功能。

打开/保存现有文件📂💾

这种方法原理基本相同,而且我觉得甚至更简单!

首先,在App.svelte文件中创建一个新的消息监听器,监听"fileopened"包含文件路径和文件内容的消息。收到此消息后,更新变量,以便 Svelte 更新我们的 HTML:

ipcRenderer.on("fileopened", (e, { path, content }) => {
    activeFilePath = path;
    markdown = content;
});
Enter fullscreen mode Exit fullscreen mode

然后在main.js文件中,与该函数相同的位置createNewFile,创建一个openFile函数,该函数将向用户显示类似的对话框,但用于选择现有文件:

const openFile = () => {
    const file = dialog.showOpenDialogSync(mainWindow, {
      properties: ["openFile"],
      filters: [{ name: "Markdown Files", extensions: ["md"] }]
    });

    if (file) {
      fs.readFile(file[0], "utf8", (err, data) => {
        if (err) return;

        mainWindow.webContents.send("fileopened", {
          path: file[0],
          content: data
        });
      });
    }
};
Enter fullscreen mode Exit fullscreen mode

我们获取用户的选择,读取文件及其路径,然后"fileopened"向我们的 Svelte 应用发送消息。同时,我们也在函数"fileopened"中发送一条消息createNewFile,以便在保存新文件时更新 activeFilePath:

...})
      .then(({ canceled, filePath }) => {
        if (canceled) return;

        fs.writeFile(filePath, content, err => {
          if (err) return;

          mainWindow.webContents.send("fileopened", {
            path: filePath,
            content
          });
        });
      });
Enter fullscreen mode Exit fullscreen mode

接下来,我们来添加一个用于打开文件的菜单项和键盘快捷键:

menu.append(
    new MenuItem({
      label: "Open",
      accelerator: "CmdOrCtrl+O",
      click: openFile
    })
);
Enter fullscreen mode Exit fullscreen mode

现在运行程序后npm start,你应该可以点击菜单中的“打开”按钮,或者按下 CTRL + O 键,弹出文件浏览器对话框。如果你选择一个现有的 Markdown 文件,它应该会在编辑器中打开。我们差不多完成了!

最后,添加一个新的菜单项,该菜单项将创建一个包含一些模板内容的新 Markdown 文件:

menu.append(
    new MenuItem({
      label: "Create New File",
      accelerator: "CmdOrCtrl+N",
      click: () =>
        createNewFile(
          "# You can enter markdown here\n\n**This text is bold**\n\n_This text is italic_"
        )
    })
);
Enter fullscreen mode Exit fullscreen mode

🎉 你现在应该可以正常使用 Markdown 编辑器了!

为您的现有平台构建 🏗️

要打包应用程序,我们可以使用名为electron-packager的 CLI 工具。

  1. 首先,请在本地安装该软件包:npm install electron-packager --save-dev

  2. 然后sirv-cli从 package.json 文件中删除该包(对于 electron 应用来说,我们不需要它)。

  3. electron包裹移入devDependencies

  4. 在 package.json 文件中添加一个指向入口文件的“main”属性:"main": "./main.js"

  5. 在 package.json 文件中添加“productName”属性:"productName": "markdown-editor"

  6. 在脚本中添加一个名为“package”的脚本:"package": "npm install && npm run build && electron-packager ."

  7. 运行npm run package以构建适用于您当前平台的 Electron 应用

这将创建一个文件夹,其中包含您的可执行文件(在 Windows 系统上)以及相关文件(在 macOS 系统上)。您可以点击此处查看 electron-packager 文档了解更多信息。

结论

我们仅用少量代码就利用 Svelte 和 Electron 构建了一个非常基础的 Markdown 编辑器。这篇博文只是一个入门指南,如果您想更深入地开发,请参考 Electron 官方文档或更详细的教程

一些补充说明

  • 我们通过在渲染进程(Svelte)和主进程(Electron)之间发送消息来使用 Electron 和 Node API。另一种方法是直接在 Svelte 文件中使用 Electron 和 Node API

  • 这是我第一次尝试使用 Electron,请将本指南/教程视为 Electron 的入门介绍,不是正确的使用方法。

PS:我将于三月份在伦敦Svelte聚会上就此主题做一个非常简短的闪电演讲。

文章来源:https://dev.to/pjaerr/building-a-desktop-app-using-svelte-and-electron-18ad