Electron + React 开发视频壁纸-从构建到打包

2,686 阅读8分钟

(一张完成图,视频放不出来。)

前言

最近在玩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 模块内的 remoteremote 返回的每个对象(包括函数)都代表了主进程中的一个对象(我们称之为远程对象或者远程函数),创建了一个函数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目录下就能看到我们打包后的文件了,一起尝试下吧。