把你的 web 网页改造为 Electron App
基于 Electron, React, React-router, Typescript 一款桌面豆瓣电影应用
源码: github.com/Yangfan2016…
web 项目源码:github.com/Yangfan2016…
作者:github.com/Yangfan2016
前言
618 在慕课网上淘了一个 1 元的 electron 课程,一个基础的入门课,还不错,就想着把前些日子写的 React+Typescript 实现一个简单的豆瓣电影应用 改造下,这次也是参(cao)考(xi)腾讯视频(mac 端 app)的 UI(腾讯视频请给我打钱,)
先来看下最终效果
准备工作
首先你要有以下的基础知识
- web 的基础知识(html,css,JavaScript)
- nodejs 的 http 部分的知识(搭建简单的 web 服务器),
- electron 的基础知识(搭建简单的electron demo、主进程和渲染进程的通信)
项目目录
先来看下最终的目录结构
可以使用 treer 来生成目录结构
$ npx treer -i "/^(node_modules|dist|build|notes|\.git|\.DS_Store)$/" >tree.txt 复制代码 douban-movie-electron ├─.yarnrc # yarn 的配置文件 ├─app.config.js # app 的全局配置 ├─main.js # app 的主进程 ├─package.json ├─src # 原有 web 项目的源码(这里忽略展开) | |...... ├─server # api 代理服务器 | ├─app.js | ├─package.json | └yarn.lock ├─scripts | ├─build.js | ├─start.js | └test.js ├─public | ├─favicon.ico | ├─index.html # 主窗口 | ├─manifest.json | ├─play.html # 播放窗口 | └renderer.js # 播放窗口的渲染进程 ├─config | ├─env.js | ├─paths.js | ├─webpack.config.js | ├─webpackDevServer.config.js | ├─jest | | ├─cssTransform.js | | └fileTransform.js ├─assets # app 的logo | ├─icon.png | └logo.png 复制代码
开发注意
- 安装
需要安装如下包依赖
"devDependencies": { "electron": "4.1.3", # electron 本体 "electron-builder": "^20.40.2", # electron 打包工具 "nodemon": "^1.18.10" # 监听文件,重启 node 应用 }
electron 包下载较慢,我们需要配置下,在项目里新增 .npmrc 或 .yarnrc 文件(如果你的项目没有使用 sass 的话,不用设置 node-sass 的镜像地址)
.npmrc
registry=https://registry.npm.taobao.org/ sass_binary_site=https://npm.taobao.org/mirrors/node-sass/ electron_mirror=https://npm.taobao.org/mirrors/electron/
.yarnrc
registry "https://registry.npm.taobao.org/" sass_binary_site "https://npm.taobao.org/mirrors/node-sass/" electron_mirror "https://npm.taobao.org/mirrors/electron/"
项目改造
- 首先写一个简单的 electron-demo
const { app, BrowserWindow } = require('electron') const path = require("path"); app.on('ready', () => { // 新建一个窗口 let mainWindow = new BrowserWindow({ width: 1160, height: 720, }); // 原有的项目开发环境下的 devServer 的端口是 3000 ,我们这里以 url 形式把原有项目加载进来 mainWindow.loadURL('http://localhost:3000'); });
- 然后我们配置 package.json 脚本(nodemon 会监听你的文件变化,重新启动 electron,不用你每次启动)
"scripts": { "start": "node scripts/start.js", "build": "node scripts/build.js", "test": "node scripts/test.js", "server": "node server/controllers/index.js", "electron-dev": "nodemon --watch ./main.js --exec 'NODE_ENV=development electron .'" }
- 执行如下命令
# 并行执行 $ yarn start & yarn electron-dev
- 由于是app,和web不同,所以我们要优化下样式,画个原型图先
推荐这个在线原型图工具 whimsical.com/
- 然后我们实现下细节,就可以看到如下效果了
- 把之前的播放器弹出框改造
同样参照腾讯视频,播放视频时,会单独弹出一个新窗口进行播放,在 electorn 里,就是再新建一个窗口
new BrowserWindow({ width: 1100, height: 500, titleBarStyle: "hiddenInset", });
现在就有一个问题,就是点击主窗口的某个电影打开这个播放窗口,那么信息(例如 视频的 src 地址)该如何传递内,我们从 electron 的文档里可以找到,ipcMain 和 ipcRenderer 这两个api,用它们进行主进程和渲染进程之间的通信
项目生产环境配置
- 生产环境
- 由于 electron 使用的是 file 协议,所以我们最后运行的就不能以 url 的形式加载了,改造如下
const { app, BrowserWindow } = require('electron') const path = require("path"); const isProd = process.env.NODE_ENV !== "development"; app.on('ready', () => { // 新建一个窗口 let mainWindow = new BrowserWindow({ width: 1160, height: 720, }); // 生产环境 if (isProd) { // cra 默认的打包目录是 build,我们生产环境需要这么引入 mainWindow.loadFile(path.join(__dirname, "./build/index.html")); } else { // 开发环境 mainWindow.loadURL('http://localhost:3000'); } });
- nodejs api 服务改造 我们需要把 nodejs 的启动文件引入进来,这样在生产环境下就不需要单独启动了
- 首先我们把 nodejs 的启动 app 暴露出来
const Koa = require("koa"); const proxy = require("koa-server-http-proxy"); const app = new Koa(); // proxy app.use(proxy('/api', { target: 'http://api.douban.com/', changeOrigin: true, pathRewrite: { '^/api': '/v2', // 重写路径 }, })); app.use(proxy('/bing', { target: 'https://www.bing.com/', changeOrigin: true, pathRewrite: { '^/bing': '/', // 重写路径 }, })); - app.listen(server.port, () => { - console.log(server.url); - }); + module.exports=app;
- 然后我们在 main.js 中引入
const apiServer = require("./server/app"); // 生产环境我们直接启动 我们的 nodejs 服务 if (isProd) { // start api server apiServer.listen(server.port, () => { console.log(server.url); }); } else { // 开发环境 我们直接启动 webpackDevServer // start webpack-devserver require("./scripts/start"); }
打包
使用 electron-builder 打包
"scripts": { "start": "node scripts/start.js", "build": "node scripts/build.js", "test": "node scripts/test.js", "server": "node server/controllers/index.js", "electron-dev": "nodemon --watch ./main.js --exec 'NODE_ENV=development electron .'", "dist": "rm -rf ./dist && electron-builder" }, // electron-builder 的配置 "build": { "appId": "douban-movie-electron", // 打包的文件目录,这样可以减少安装包大小 "files": [ "package.json", "node_modules/**/*", "build/**/*", "assets", "server", "main.js", "app.config.js" ], // mac 端的打包配置下,详细配置 https://www.electron.build/configuration/mac "mac": { "category": "public.app-category.video", mac 应用程序分类 https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/LaunchServicesKeys.html#//apple_ref/doc/uid/TP40009250-SW8 "target": [ "dmg" ], "icon": "./assets/icon.png" } },
执行命令
$ yarn dist
安装
打包的文件会在 dist 文件夹下,我们找到 dmg 文件,进行安装
安装完成,进行预览,发现 ,哎,首页的轮播图加载失败了 (404),我仔查看路径,没有任何问题,但是发现 它们的路径有一个特点就是都是 file:///static 开头的(本地图片以相对路径引用的,其他 js css 文件也是以相对路径引入的,只有图片404,奇怪 )
几经周折,终于找到了问题,React-router 我用的是 browserRouter ,就是基于浏览器 history api的,改成 hashRouter ,就好了(目前原因未知 ,知道原因的小伙伴请教下)
时间有限,其他平台的打包方式及配置,请移步到 electron-builder 官网