本文所有源码均在:github.com/Sunny-117/e...
本文收录在《Electron桌面客户端应用程序开发入门到原理》掘金专栏
本文介绍
同样是通过不断迭代的方式,一步一步完善音乐播放器。
- 原生网页版音乐播放器
- AmplitudeJS迭代音乐播放器
- Electron版本音乐播放器
- Vite、Vue、Electron搭建一个项目,继续迭代音乐播放器
- Electron-Vite 迭代音乐播放器
AmplitudeJS
AmplitudeJS 是一个轻量级和开源的 JavaScript 库,它建立在 HTML5 音频 API 之上,提供了一套易于使用的接口来管理音频播放、播放列表、音量控制等。
官网地址:521dimensions.com/open-source...
AmplitudeJS 具有以下特点:
- 无依赖:AmplitudeJS 不依赖于任何其他 JavaScript 库或框架,这使得它在任何项目中都很容易集成。
- 自定义 UI:开发者可以自由设计和实现音频播放器的用户界面,使其与应用的风格一致。AmplitudeJS 不强加任何样式,它只提供功能性的接口。
- 丰富的 API:AmplitudeJS 提供了广泛的 API,使得开发者可以通过编程的方式控制播放器的行为,如播放、暂停、跳转到特定时间点等。
- 播放列表和歌曲管理:它支持创建和管理多个播放列表,以及播放列表内歌曲的动态添加和删除。
快速上手案例
下面是一个关于 AmplitudeJS 的一个快速入门案例:
js
// index.js
Amplitude.init({
songs: [
{
name: "Gotta Have You",
url: "./music/Gotta Have You.mp3",
},
{
name: "K歌之王",
url: "./music/K歌之王 - 陈奕迅.mp3",
},
// ...
],
volume: 50,
});
首先需要在 html 中引入 amplitude.js。引入之后会提供一个 Amplitude 的对象,上面会有大量的方法,这里我们暂时只用到了 init 方法。该方法用于初始化我们要播放的歌曲,以及一开始的音量大小。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h1>Music player</h1>
<button class="amplitude-play">播放</button>
<button class="amplitude-pause">暂停</button>
<div class="amplitude-play-pause">播放/暂停</div>
<script src="./amplitude.js"></script>
<script src="./index.js"></script>
</body>
</html>
接下来就是 html 部分的代码,首先需要引入 amplitude.js 以及刚才我们自己所写的 index.js。
之后在 html 元素上挂相应的样式类就可以了,例如挂上一个 amplitude-play 类,那么这个元素就是一个"播放"按钮,挂上一个 amplitude-pause 类,那么这个元素就是一个"暂停"按钮。这使得我们不再需要去编写和 audio 元素相关的"播放"或者"暂停"这部分逻辑。而样式部分,则完全交给用户,让用户来自定义。
另外,在 AmplitudeJS 中定义了 4 个不同的级别来控制音频的播放:
- 全局(Global):全局元素控制任何正在播放的音频,无论范围如何。
html
<!-- 全局播放/暂停按钮 -->
<button class="amplitude-play-pause" id="global-play-pause"></button>
- 播放列表(Playlist):播放列表级别的元素只影响特定播放列表中的音频。
html
<!-- 特定播放列表的播放/暂停按钮 -->
<button class="amplitude-play-pause" data-amplitude-playlist="playlist_key" id="playlist-play-pause"></button>
- 歌曲(Song):歌曲级别的元素仅影响或显示单个歌曲,不考虑它是否属于某个播放列表。
html
<!-- 控制特定歌曲的播放/暂停按钮 -->
<button class="amplitude-play-pause" data-amplitude-song-index="song_index" id="song-play-pause"></button>
- 播放列表中的歌曲(Song In Playlist):这些元素影响或显示播放列表中的特定歌曲。
html
<!-- 控制播放列表中特定歌曲的播放/暂停按钮 -->
<button class="amplitude-play-pause" data-amplitude-song-index="song_index_in_playlist" data-amplitude-playlist="playlist_key" id="song-in-playlist-play-pause"></button>
常用类记录
-
amplitude-prev:上一曲
-
amplitude-next:下一曲
-
amplitude-play-pause:播放和暂停
-
amplitude-current-minutes:当前播放的分钟数
-
amplitude-current-seconds:当前播放的秒数
-
amplitude-duration-minutes:总的分钟数
-
amplitude-duration-seconds:除开总分钟数后的剩余秒数
-
amplitude-song-slider:播放进度条
-
amplitude-mute:静音操作
-
amplitude-volume-slider:控制音量大小
-
data-amplitude-song-info:获取歌曲信息,这个歌曲信息来源于在使用 Amplitude.init 方法初始化歌曲时传入的歌曲信息
- data-amplitude-song-info="name" :就是获取歌曲的名称
集成现代框架
这里我们会集成两个东西:
- Vue3
- Vite
首先第一步,我们需要使用 Vite 来搭建一个基于 Vue 的项目。命令如下:
bash
npm create vite@latest <项目名> -- --template vue
项目搭建完毕后,接下来需要安装 electron:
bash
npm install electron -D
之后,我们需要创建我们的主进程代码,代码如下:
js
// src/main/mainEntry.js
// 和主进程相关的代码
import { app, BrowserWindow } from "electron";
let mainWindow = null; // 存储窗口实例
app.whenReady().then(() => {
mainWindow = new BrowserWindow({});
mainWindow.loadURL(process.argv[2]);
});
process.argv 拿到的是一个数组,数组里面会包含启动 Node.js 进程时传递给它的命令行参数。
- process.argv[0] :Node.js 的路径
- process.argv[1] :正在执行的 JavaScript 文件的路径
- process.argv[2] :从 2 开始,也就是数组的第三项开始,蚕食实际命令行传递给这个脚本的第一个实际参数,该参数回头会对应一个 URL
开发 Vite 插件
接下来,我们需要针对 Vite 编写一个插件:
js
import esbuild from "esbuild";
import { spawn } from "child_process";
import electron from "electron";
export const devPlugin = () => {
return {
name: "dev-plugin", // 插件的名称
// 一个异步的方法,用于配置服务器的
// 接收一个参数,该参数就是 Vite 开发服务器的实例
async configureServer(server) {
// 首先第一步,咱们需要使用 esbuild 去同步的构建项目
esbuild.buildSync({
entryPoints: ["./src/main/mainEntry.js"], // 对象项目的入口文件
bundle: true, // 启用打包,将依赖一起打包为一个文件
platform: "node", // 指定平台为 node,主要是为了 Electorn 主进程服务
format: "esm", // 模块的格式
outfile: "./dist/mainEntry.js", // 输出文件的路径
external: ["electron"], // 外部依赖,避免被打包进去
});
// 接下来,我们需要监听服务器的 listening 事件
// 这个 listening 事件会在服务器开始监听端口时触发
server.httpServer.listen("listening", () => {
// 当触发 listening 事件的时候,我们需要启动 electron 进程
// 获取服务器的地址信息,包括 IP 和端口
const addressInfo = server.httpServer.address();
// 构造服务器的 HTTP 地址字符串
const httpAddress = `http://${addressInfo.address}:${addressInfo.port}`;
// 启动 electron 进程
const electronProcess = spawn(
electron,
["./dist/mainEntry.js", httpAddress],
{
cwd: process.cwd(), // 子进程 electron 当前的工作目录
stdio: "inherit", // 继承父进程的标准输入输出
}
);
// 监听 electron 的 close 事件
electronProcess.on("close", () => {
server.close(); // 关闭 Vite 开发服务器
process.exit(); // 退出当前进程
});
});
},
};
};
这里介绍一下关于 spawn。这个 spawn 是 child_process 模块里面的一个方法,该方法用于异步的创建一个新的子进程。
spawn 方法接收这么一些参数:
- command:要运行的命令
- args:命令的参数列表,是一个字符串数组
- options:可选的配置对象,可以配置:
- cwd:子进程当前的工作目录
- env:环境变量键值对
- shell:对应的是一个布尔值或者字符串
- 如果是布尔值,例如是 true ,代表在 shell 中运行命令
- 如果是字符串,代表的就是具体的要执行的 shell 命令
插件编写完毕之后,接下来就是使用插件:
js
// vite.config.js
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { devPlugin } from "./plugins/devPlugin";
// https://vitejs.dev/config/
export default defineConfig({
// 使用刚才我们所编写的插件
plugins: [devPlugin(), vue()],
});
集成现代框架
这里我们会集成两个东西:
- Vue3
- Vite
首先第一步,我们需要使用 Vite 来搭建一个基于 Vue 的项目。命令如下:
bash
npm create vite@latest <项目名> -- --template vue
项目搭建完毕后,接下来需要安装 electron:
bash
npm install electron -D
之后,我们需要创建我们的主进程代码,代码如下:
js
// src/main/mainEntry.js
// 和主进程相关的代码
import { app, BrowserWindow } from "electron";
let mainWindow = null; // 存储窗口实例
app.whenReady().then(() => {
mainWindow = new BrowserWindow({});
mainWindow.loadURL(process.argv[2]);
});
process.argv 拿到的是一个数组,数组里面会包含启动 Node.js 进程时传递给它的命令行参数。
- process.argv[0] :Node.js 的路径
- process.argv[1] :正在执行的 JavaScript 文件的路径
- process.argv[2] :从 2 开始,也就是数组的第三项开始,蚕食实际命令行传递给这个脚本的第一个实际参数,该参数回头会对应一个 URL
开发 Vite 插件
接下来,我们需要针对 Vite 编写一个插件:
js
import esbuild from "esbuild";
import { spawn } from "child_process";
import electron from "electron";
export const devPlugin = () => {
return {
name: "dev-plugin", // 插件的名称
// 一个异步的方法,用于配置服务器的
// 接收一个参数,该参数就是 Vite 开发服务器的实例
async configureServer(server) {
// 首先第一步,咱们需要使用 esbuild 去同步的构建项目
esbuild.buildSync({
entryPoints: ["./src/main/mainEntry.js"], // 对象项目的入口文件
bundle: true, // 启用打包,将依赖一起打包为一个文件
platform: "node", // 指定平台为 node,主要是为了 Electorn 主进程服务
format: "esm", // 模块的格式
outfile: "./dist/mainEntry.js", // 输出文件的路径
external: ["electron"], // 外部依赖,避免被打包进去
});
// 接下来,我们需要监听服务器的 listening 事件
// 这个 listening 事件会在服务器开始监听端口时触发
server.httpServer.listen("listening", () => {
// 当触发 listening 事件的时候,我们需要启动 electron 进程
// 获取服务器的地址信息,包括 IP 和端口
const addressInfo = server.httpServer.address();
// 构造服务器的 HTTP 地址字符串
const httpAddress = `http://${addressInfo.address}:${addressInfo.port}`;
// 启动 electron 进程
const electronProcess = spawn(
electron,
["./dist/mainEntry.js", httpAddress],
{
cwd: process.cwd(), // 子进程 electron 当前的工作目录
stdio: "inherit", // 继承父进程的标准输入输出
}
);
// 监听 electron 的 close 事件
electronProcess.on("close", () => {
server.close(); // 关闭 Vite 开发服务器
process.exit(); // 退出当前进程
});
});
},
};
};
这里介绍一下关于 spawn。这个 spawn 是 child_process 模块里面的一个方法,该方法用于异步的创建一个新的子进程。
spawn 方法接收这么一些参数:
- command:要运行的命令
- args:命令的参数列表,是一个字符串数组
- options:可选的配置对象,可以配置:
- cwd:子进程当前的工作目录
- env:环境变量键值对
- shell:对应的是一个布尔值或者字符串
- 如果是布尔值,例如是 true ,代表在 shell 中运行命令
- 如果是字符串,代表的就是具体的要执行的 shell 命令
插件编写完毕之后,接下来就是使用插件:
js
// vite.config.js
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { devPlugin } from "./plugins/devPlugin";
// https://vitejs.dev/config/
export default defineConfig({
// 使用刚才我们所编写的插件
plugins: [devPlugin(), vue()],
});
目前,我们已经成功集成了 electron、vite 以及 vue 这几个模块。但是现在存在一个问题,就是在**渲染进程**下面是无法使用 Node.js 的模块以及 electron 自身的模块,即便我们在主进程中创建窗口的时候,已经明确的指明了要集成 node.js 以及关闭上下文隔离,在渲染进程中仍然无法使用。究其原意,是因为 vite 主动屏蔽了这些模块,如果开发者强行要引入这些所屏蔽的模块,那么就会出现诸如下面的错误:
rust
Module "xxxx" has been externalized for browser compatibility and cannot be accessed in client code.
要解决这个问题,我们就需要安装一个插件:vite-plugin-optimizer
该插件会为你创建一个临时的目录:node_modules.vite-plugin-optimizer
然后会将类似于:
js
const fs = require('fs'); export {fs as default}
这样的代码写入到临时目录的 fs.js 文件中。
之前我们在渲染进程里面执行这样的代码 import fs from "fs"; 是找不到的,但是现在执行这样的代码的时候,就会请求临时目录下的 fs.js 模块,从而达到了在渲染进程中引入 Node.js 内置模块的目录。
接下来在 vite 中新增一个插件,叫做 getReplacer,对应的代码如下:
js
export const getReplacer = () => {
// 在这个插件里面,我们主要要做的事情就是替换工作
// 这里的替换工作包含两个方面:
// 1. Node.js 常见的模块替换,比如 path、fs、os 等
// 2. Electron 相关的内置模块,比如 clipboard,ipcRenderer 等
// 该数组存放了一些 Node.js 下常用模块
let externalModels = [
"os",
"fs",
"path",
"events",
"child_process",
"crypto",
"http",
"buffer",
"url",
"better-sqlite3",
"knex",
];
// 该对象用于存储最终的替换结果
let result = {};
for (let item of externalModels) {
result[item] = () => ({
find: new RegExp(`^${item}$`),
code: `const ${item} = require('${item}'); export { ${item} as default }`,
});
}
// 处理 electron 对应的模块,处理的思路和上面的 node.js 的处理思路是一样。
result["electron"] = () => {
let electronModules = [
"clipboard",
"ipcRenderer",
"nativeImage",
"shell",
"webFrame",
].join(",");
return {
find: new RegExp(`^electron$`), // 使用该正则去匹配 electron 模块
code: `const { ${electronModules} } = require('electron'); export { ${electronModules} }`, // 要生成的代码片段
};
};
return result;
};
该方法会返回一个对象,该对象类似于:
js
{
electron: `const { ipcRenderer } = require('electron'); export { ipcRenderer };`,
fs: () => ({
find: /^(node:)?fs$/,
code: `const fs = require('fs'); export { fs as default }`;
}),
}
最终,我们在 vite.config.js 中使用该插件:
js
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { devPlugin, getReplacer } from "./plugins/devPlugin";
import optimizer from "vite-plugin-optimizer";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [optimizer(getReplacer()), devPlugin(), vue()],
});
这样就大功告成了,可以在渲染进程中使用 node.js 以及 electron 内置的模块了。
electron-vite介绍
前面我们从零开始使用 vite + vue3 搭建了一个 electron 的开发环境,并且迭代了前面我们所写的音乐播放器,这一节课给大家介绍一个比较有名的脚手架:electron-vite
我们直接使用如下的命令搭建一个项目出来:
bash
npm create @quick-start/electron
具体操作如下图所示:
之后安装依赖,然后 npm run dev 把项目跑起来即可。
目录结构
到目前为止都很轻松,接下来我们需要熟悉这个项目,那么就从项目的目录结构开始熟悉。
这里我们主要关注这么几个目录
-
src 目录:这是我们主要的开发目录
-
main:主进程相关代码
-
preload:预加载脚本
-
renderer:渲染进程相关代码,使用 vue 相关技术
-
-
build 目录:构建后的目录,存放构建后的文件
-
out 目录:打包后的目录,打包后的文件就存放于此目录中,electron 实际上加载的是此目录里面的内容
-
resources 目录:公共资源目录,如果你有图标、可执行程序、wasm 文件等资源,可以将它们放在这个目录中。
- 公共目录中的所有资源都不会复制到输出目录。所以在打包 app 的时候,公共目录应该一起打包。
- 渲染进程中的公共资源处理不同于主进程和预加载脚本。
- 默认情况下,渲染进程的工作目录位于
src/renderer
,因此需要在该目录下创建公共资源目录。默认的公共目录名为public
,也可以通过renderer.build.publicDir
指定。 - 渲染进程的公共资源将被复制到输出目录。
- 默认情况下,渲染进程的工作目录位于
-
electron-builder.yml 文件:和打包相关的配置文件,里面配置了不同操作系统,打包成不同产物的配置
热加载
很多时候,我们希望主进程或预加载脚本模块发生变化时,能够快速重新构建并重启 Electron 程序。
使用 CLI 选项的 -w 或者 --watch 即可,这是首选方式,它更加灵活。
json
"scripts": {
...
"dev": "electron-vite dev --watch",
...
},
本文所有源码均在:github.com/Sunny-117/e...
「❤️ 感谢大家」
如果你觉得这篇内容对你挺有有帮助的话: 点赞支持下吧,让更多的人也能看到这篇内容(收藏不点赞,都是耍流氓 -_-)欢迎在留言区与我分享你的想法,也欢迎你在留言区记录你的思考过程。觉得不错的话,也可以阅读 Sunny 近期梳理的文章(感谢掘友的鼓励与支持 🌹🌹🌹):
我的博客:
Github: https://github.com/sunny-117/
前端八股文题库: sunny-117.github.io/blog/
前端面试手写题库: github.com/Sunny-117/j...
手写前端库源码教程: sunny-117.github.io/mini-anythi...
热门文章
- ✨ 爆肝 10w 字,带你精通 React18 架构设计和源码实现【上】
- ✨ 爆肝 10w 字,带你精通 React18 架构设计和源码实现【下】
- 前端包管理进阶:通用函数库与组件库打包实战
- 🍻 前端服务监控原理与手写开源监控框架 SDK
- 🚀 2w 字带你精通前端脚手架开源工具开发
- 🔥 爆肝 5w 字,带你深入前端构建工具 Rollup 高阶使用、API、插件机制和开发
- 🚀 Rust 构建简易实时聊天系统
专栏