(一张完成图,视频放不出来。)
前言
最近在玩electron,所以有了这篇文章,看完本篇文章,你将会学会如何构建一个桌面端项目,并会使用electron配合react完成一个桌面端应用并打包,如果你认可的话,请给一个赞,鼓励一下,谢谢。
项目搭建
首先我们先创建下我们的目录 (根据喜好创建目录,不要求一样)
├── package.json ---项目的配置(npm init 创建)
├── app ---应用的存放地
| ├── main ---主进程的存放地
| ├── randerer ---渲染进程的存放地
| ├── product ---react的存放地(利用create-react-app创建)
├── dist ---应用打包后的存放地
第一步:创建项目
先初始化我们的项目
目录:/
npm init -y
然后到app目录下创建一个react项目
目录:/app
使用 npx create-react-app product
想使用ts的小伙伴可以用下面那条
目录:/app
使用 npx create-react-app product --typescript
我这里就不用了,安装完的话可以cd到product运行看下。
第二步:安装所需并配置
我们安装一下 react-app-rewired 和 cross-env 和 customize-cra
react-app-rewired 用于我们扩展webpack
cross-env 能跨平台地设置及使用环境变量,如果你的应用需要跨平台,使用它可以让你更方便。
customize-cra 是依赖于 react-app-rewired 的库,通过 config-overrides.js 来修改底层的 webpack
目录:/app/product
使用 npm i -D react-app-rewired customize-cra cross-env
我们到product目录下创建 config-overrides.js 配置我们的应用
目录:/app/product/config-overrides.js
const { override, addLessLoader } = require("customize-cra");
module.exports = override(
//使用less 需要安装 less + lessloader 我们重点不是样式,所以大家按需使用
addLessLoader(),
(config, env) => {
//修改target,告诉webpack我们在用electron,webpack会做些特殊处理,不然我们引入electron模块时,会解析不了。
//或者使用window.require来解决
config.target = "electron-renderer";
return config;
}
);
安装electron
目录:/
npm i -D electron //ps:可以用cnpm
因为开发时我们electron加载的localhost:3000而打包后我们需要加载的是index.html 所以我们需要判断是否是开发环境来按需加载页面,我们可以安装 electron-is-dev
目录: /
npm i -S electron-is-dev
安装完成后我们在app目录下的main目录下创建我们的主进程文件 index.js
目录:/app/main/index.js
const {app,BrowserWindow} = require("electron");
const isDev = require("electron-is-dev");
const path = require("path")
let mainWin = null;
app.on("ready",()=>{
mainWin = new BrowserWindow({
width:300,
height:300,
webPreferences:{
nodeIntegration:true //使用node
}
})
if (isDev) {
mainWindow.loadURL("http://localhost:3000");
} else {
mainWindow.loadURL(path.resolve(__dirname, "../product/build/index.html"));
}
mainWindow.on("closed", ()=> {
mainWindow = null;
});
})
打开根目录的 package.json 配置下我们的环境变量
目录:/package.json
"main": "app/main/index", //主进程入口
"scripts": {
"start": "cd app/product && npm run start",
"start-electron": "electron ."
},
目录:/app/product/package.json
"homepage": "./",
"scripts": {
"start": "cross-env BROWSER=none react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test --env=jsdom",
"eject": "react-scripts eject",
},
运行下我们的electron看下是否安装并配置成功。
目录:/
npm run start
npm run start-elecron
开发
我的项目正常运行了,下面开始开发,我们的重点不在样式,所以我就不写样式了,先讲一下思路。
点击设置壁纸按钮 => 选择视频文件 => 设置为壁纸
那下面开始开发,我们需要先拿到视频存放的路径。
目录:/app/product/src/App.js
import { remote } from "electron";
const dialog = remote.dialog;
function selectVideo() {
let videoPath = dialog.showOpenDialogSync({
title: "选择视频",
buttonLabel: "确认",
filters: [{ name: "视频", extensions: ["mp4"] }],
});
console.log(videoPath[0]);
//C:\xxx\xxx\xxx.mp4
}
function App() {
return (
<div className="App">
<button onClick={selectVideo}>点击设置壁纸</button>
</div>
);
}
当点击按钮时我们去获取视频地址,这段代码里我们引入了 electron
模块内的 remote
,remote
返回的每个对象(包括函数)都代表了主进程中的一个对象(我们称之为远程对象或者远程函数),创建了一个函数selectVideo
选择视频并输出地址,dialog
显示用于打开和保存文件、警报等的本机系统对话框,其返回的是一个数组。
最后我们新创建一个窗口将窗口放到壁纸层,再将视频设置为全屏就可以啦,当然我们的js做不到这些,所以我们需要调用 c++ 能力来扩展我们的js。
集成c++能力
我们有一下两种方案
1.n-api 可以C/C++ 写node的扩展,但我不会c/c++ 所以不考虑,感兴趣的小伙伴可以去研究一下。 2.node-ffi (node版本 < 10 , electron版本 < 6 ) ffi-napi (node版本 > 10 , electron版本 >= 6 )
所以我们用ffi-napi,我们可以用这个去调用windows系统的api执行我们所需要的函数,这里就不细讲了,感兴趣的小伙伴可以去研究一下,我们用 win-win-api
里面封装了windows系统部分api,以上模块都需要安装 node-gyp
进行编译才可使用,那么我们下面来安装node-gyp
吧。
node-gyp 安装
首先node-gyp的安装很坑,windows安装比较容易劝退,大家跟我一步一步来。
安装node-gyp你需要py2.7和c++的工具链,以管理员运行cmd执行以下命令,这条命令可以自动帮你安装但我没安装成功,windows系统不同版本有不同的问题,如果自动安装不了的话可以手动安装一下。
npm i -g windows-build-tools
安装成功后设置下vs版本
npm config set msvs_version 2017
指定一下python
npm config set python path(py所在的目录)
都配置好了后可以安装 node-gyp
啦
npm i -g node-gyp
安装完后我们下载一下编译包
node-gyp install --dist-url=https://npm.taobao.org/mirrors/node
可能下载会404 我们需要手动下载一下
https://npm.taobao.org/mirrors/node/
找到你对应node版本的文件夹进去下载
这是我的版本所在的目录
https://npm.taobao.org/mirrors/node/v12.18.3/win-x64/node.lib
手动下载后放到
C:\Users\xxx\.node-gyp\12.18.3\x64
好的,我们一切准备就绪,下面开始安装win-win-api
目录:/
npm i -S win-win-api@0.0.4
因为里面带了 ffi-napi
所以 node-gyp
会自动编译它,编译成功后我们就可以引入使用了。
我们先创建一个子窗口,通过windows的api将它设置到壁纸层,感兴趣的可以去看下ffi的用法。
目录:/app/main/index.js
const ipc = require("electron").ipcMain;
const { WinWin, ffi, CPP, L, NULL } = require("win-win-api");
const winFns = new WinWin().winFns();
const os = require("os");
let wallWindow = null;
ipc.on("setWallpaper", (e, args) => {
if (!wallWindow) {
wallWindow = new BrowserWindow({
opacity: 0,//初始透明值
transparent: true, //窗口透明
frame: false, //是否显示边缘框
fullscreen: true, //是否全屏显示
webPreferences: {
nodeIntegration: true, //赋予此窗口页面中的JavaScript访问Node.js环境的能力
enableRemoteModule: true, //打开remote模块
webSecurity: false, //可以使用本地资源
},
});
wallWindow.loadURL(path.resolve(__dirname, "../renderer/wallPaper.html"));
SetParent(wallWindow);//将此窗口设置为桌面的子窗口
//之后选择的视频地址可以直接传过去,加载。
wallWindow.webContents.send("setVideo", args);
//第一次创建窗口,渲染完加载一次,因为异步。
wallWindow.webContents.on("did-finish-load", () => {
wallWindow.webContents.send("setVideo", args);
});
wallWindow.on("close", () => {
wallWindow = null;
});
}
});
function SetParent(childWindow) {
//壁纸句柄
let workView = null;
//寻找底层窗体句柄
let Progman = winFns.FindWindowW(L("Progman"), NULL);
//使用 0x3e8 命令分割出两个 WorkerW
winFns.SendMessageTimeoutW(Progman, 0x052c, 0, 0, 0, 0x3e8, L("ok"));
//创建回调函数
const createEnumWindowProc = () =>
ffi.Callback(CPP.BOOL, [CPP.HWND, CPP.LPARAM], (tophandle) => {
//寻找桌面句柄
let defview = winFns.FindWindowExW(
tophandle,
0,
L("SHELLDLL_DefView"),
NULL
);
// 如果找到桌面句柄再找壁纸句柄
if (defview != NULL) {
workView = winFns.FindWindowExW(0, tophandle, L("WorkerW"), NULL);
}
return true;
});
//遍历窗体获得窗口句柄
winFns.EnumWindows(createEnumWindowProc(), 0);
//获取electron的句柄
const myAppHwnd = bufferCastInt32(childWindow.getNativeWindowHandle());
//将buffer类型的句柄进行转换
function bufferCastInt32(buf) {
return os.endianness() == "LE" ? buf.readInt32LE() : buf.readInt32BE();
}
//将electron窗口设置在壁纸上层
winFns.SetParent(myAppHwnd, workView);
}
目录:/app/product/src/App.js
//通信模块,方便与主进程跟渲染进程通信
import { remote, ipcRenderer } from "electron";
const dialog = remote.dialog;
function selectVideo() {
let videoPath = dialog.showOpenDialogSync({
title: "选择视频",
buttonLabel: "确认",
filters: [{ name: "视频", extensions: ["mp4"] }],
});
//如果选择了视频,就给主进程发送个命令,创建子窗口,并设置到壁纸层,再由主进程把选择的路径给壁纸层的渲染进程。
if (videoPath[0]) {
ipcRenderer.send("setWallpaper",videoPath[0];
}
}
现在我们已经创建了壁纸层的窗口,并将他设置为桌面的子窗口,下面我们写下壁纸层窗口加载的wallPaper
页面。
目录:/app/randerer/wallPaper.html
<script>
const { ipcRenderer } = require("electron")
ipcRenderer.on('setVideo', (e, args) => {
//判断有没有加载过video,没有就先创建一个然后加载。
let videoId = document.getElementById('video')
if (videoId) {
videoId.src = args
} else {
let body = document.querySelector("body");
let video = document.createElement("video");
video.src = args
video.id = 'video'
video.autoplay = true
video.className = 'video' //class可以把视频大小设置为 windth:100 height:100
video.loop = true
body.appendChild(video)
}
});
</script>
</html>
现在大家已经可以把视频变成壁纸啦,下面我们开始打包吧。
打包
打包的工具我们选择electron-build,electron-build可以定制化打包,还可以跨平台打包,但不建议跨平台打包。
先安装一下打包工具
npm i -D electron-builder / cnpm i -D electron-builder
在package.json
进行打包配置
目录:/package.json
"scripts": {
"electron-builder":"electron-builder" //加上打包命令
},
"build": {
"appId": "com.xxx.xxx",
"productName": "Js Wallpaper", // 项目名称
"directories": {
"output":"release"//存放正式打包的路径
},
"asar":true,//是否使用asar加密
"files": [ // 需要打包的内容
"./app/renderer",
"./app/product/build",
"./app/main/index.js",
"./package.json"
],
"win": { //图标路径
"icon": "icon.ico"
}
}
配置好后我们先打包一下我们的项目
目录:/app/product
npm run build
目录:/
npm run electron-builder
如图这样就是打包成功了
最后我们到dist目录下就能看到我们打包后的文件了,一起尝试下吧。