在开发 electron 桌面端的时候,会遇到各种各样的坑,稍微总结下。
由于网络问题,导致 yarn 或者 npm 安装的时候,会报错
解决方案
sudo npm install -g cross-env
cross-env npm_config_electron_mirror="https://npm.taobao.org/mirrors/electron/" npm_config_electron_custom_dir="9.4.0" npm install
这里的 9.4.0 就是你所想安装的electron 版本。
本地数据库有很多可选的,
可以同时支持web端和桌面端使用。
IndexedDB是Chromium内置的一个基于JavaScript的面向对象的数据库,在Electron应用内它存储的容量限制与用户的磁盘容量有关,是用户磁盘大小的1/3.
特点:
关系型数据库,具有关系型数据库的一切特性,事务遵循ACID属性。小巧轻便,有knex这样的库做ORM。
是node原生模块,需要重新编译,而且有坑。
npm install sqlite3 --save
npm install electron-rebuild --save
执行 electron-rebuild,重新编译electron。
基于Loadsh的纯JSON文件数据库,速度较慢.
不支持索引/事务/批量操作等数据库功能
Small JSON database for Node, Electron and the browser. Powered by Lodash. ⚡️
适合简单存储
Easily write and read user settings in Electron apps
自带的存储。
坑点:
LocalStorage存储容量也很小,大概不会超过10M,它是以键值对形式保存数据的,同样也没有关联查询、条件查询的机制
SessionStorage最大的问题是,每次关闭应用程序,它里面的内容会被清空,想持久化存储数据,就不用考虑它了
Cookies存储容量太小,只能存4kb的内容,而且每次与服务端交互,同域下的Cookie还会被携带到服务端,也没有关联查询、条件查询的机制
一般大型项目 首选 SQLite
或者 indexedDB
,如果一些用户设置上面的设置 可以通过 electron-json-storage-alt
实现。
推荐使用electron-updater
或者自己写升级模块。
import { autoUpdater } from 'electron-updater';
let downloading = false;
function checkForUpdates() {
if (downloading) {
dialog.showMessageBox({
type: 'info',
buttons: ['OK'],
title: pkg.name,
message: `Downloading...`,
detail: `Please leave the app open, the new version is downloading. You'll receive a new dialog when downloading is finished.`
});
return;
}
autoUpdater.checkForUpdates();
}
autoUpdater.on('update-not-available', e => {
dialog.showMessageBox({
type: 'info',
buttons: ['OK'],
title: pkg.name,
message: `${pkg.name} is up to date :)`,
detail: `${pkg.name} ${pkg.version} is currently the newest version available, It looks like you're already rocking the latest version!`
});
console.log('Update not available.');
});
autoUpdater.on('update-available', e => {
downloading = true;
checkForUpdates();
});
autoUpdater.on('error', err => {
dialog.showMessageBox({
type: 'error',
buttons: ['Cancel update'],
title: pkg.name,
message: `Failed to update ${pkg.name} :(`,
detail: `An error occurred in retrieving update information, Please try again later.`,
});
downloading = false;
console.error(err);
});
autoUpdater.on('update-downloaded', info => {
var { releaseNotes, releaseName } = info;
var index = dialog.showMessageBox({
type: 'info',
buttons: ['Restart', 'Later'],
title: pkg.name,
message: `The new version has been downloaded. Please restart the application to apply the updates.`,
detail: `${releaseName}\n\n${releaseNotes}`
});
downloading = false;
if (index === 1) {
return;
}
autoUpdater.quitAndInstall();
setTimeout(() => {
mainWindow = null;
app.quit();
});
});
可以在项目启动或者页面激活状态的时候,请求服务器端最新版本号,如果存在版本过低的情况可以通知用户下载升级。
同样的是利用electron
下载文件,执行升级。
执行执行升级exe
执行 child_process
的 exec
调用 unzip -o latestPathMacZip
将 zip 文件解压成 latest.asar 文件,然后调用 renameSync
将原本的app.asar 修改成一个临时名字,将最新的latest.asar
修改成 app.asar
名字,然后最后删除掉临时名字就行了。
可以通过 mac 自带的hdiutil
指令。
一般的桌面端程序,都需要检查网络连接情况,友好的提示给客户。
在 window 里面可以借助network-interface
,来监听网络连接情况。
https://www.npmjs.com/package/network-interface
const networkInterface = require('network-interface');
networkInterface.addEventListener('wlan-status-changed', (error, data) => {
if (error) {
throw error;
return;
}
console.log('event fired: wlan-status-changed');
console.log(data);
});
对于桌面端,主进程是会有http请求的。 例如升级啥的,或者下载文件等,都是需要主进程去执行http请求。
net 模块是一个发送 HTTP(S) 请求的客户端API。 它类似于Node.js的HTTP 和 HTTPS 模块 ,但它使用的是Chromium原生网络库来替代Node.js的实现,提供更好的网络代理支持。 It also supports checking network status.
net 的优势
const { net } = require('electron')
const os = require('os');
const urllib = require('urllib');
const Agent = require('agentkeepalive');
const {HttpsAgent} = require('agentkeepalive');
const {electron: electronVersion} = process.versions;
const config = {
defaultArgs: {
timeout: 30000,
dataType: 'json',
followRedirect: true
},
httpAgent: {
keepAlive: true,
freeSocketTimeout: 20000,
maxSockets: Number.MAX_SAFE_INTEGER,
maxFreeSockets: 256
},
httpsAgent: {
keepAlive: true,
freeSocketTimeout: 20000,
maxSockets: Number.MAX_SAFE_INTEGER,
maxFreeSockets: 256
}
};
class HttpClient extends urllib.HttpClient2 {
constructor(app) {
const {pkg} = app.config;
super({
defaultArgs: config.defaultArgs,
agent: new Agent(config.httpAgent),
httpsAgent: new HttpsAgent(config.httpsAgent)
});
this.app = app;
this.logger = app.getLogger('httpClientLogger');
this.UA = `${pkg.name}/${pkg.version};electron/${electronVersion};${encodeURIComponent(os.hostname())};${urllib.USER_AGENT}`;
}
async request(url, options = {}) {
const {app} = this;
const {host} = app.config || '';
let request;
options.headers = {
"Content-Type": "application/json",
referer: host,
"user-agent": this.UA,
...options.headers
};
const nowDate = Date.now();
let error;
try {
return request = await super.request(url, options);
} catch (e) {
error = e;
error.name = 'httpError';
error.url = url;
throw error;
} finally {
// 一次请求的时间差
const timestamp = Date.now() - nowDate;
// logger 日志记录
console.log(timestamp);
if (!options.disableLogger) {
this.logger.info([url, options.method, request && request.status, error && error.message].join("^"));
}
}
}
}
module.exports = (app => {
app.httpClient = new HttpClient(app);
})
axios 是支持在node.js的。可以在electron 主进程中使用。
import axios from 'axios';
axios.defaults.baseURL = process.env.VUE_APP_BASE_URL;
<!--强制使用node模块。-->
axios.defaults.adapter = require('axios/lib/adapters/http');
// 请求拦截 设置统一header
axios.interceptors.request.use(
config => {
return config;
},
error => {
console.log(error);
return Promise.reject(error);
}
);
axios.interceptors.response.use(
response => {
return response;
},
error => {
//
console.error(error);
return Promise.reject(error);
}
);
export default axios;
文件下载,需要借助electron
提供的getPath
方法,一般默认的是下载目录下面
const downloadPath = electronApp.getPath('downloads');
const http = require('http');
const fs = require('fs');
const downloadFileAsync = (uri, dest) => {
return new Promise((resolve, reject) => {
let file = '';
try {
// 确保dest路径存在,已经存在则删除重新下载
if (fs.existsSync(dest)) {
fs.unlinkSync(dest);
}
const path = process.platform === 'win32'
? dest.slice(0, dest.lastIndexOf('\\'))
: dest.slice(0, dest.lastIndexOf('/'));
fs.mkdirSync(path, { recursive: true });
file = fs.createWriteStream(dest);
} catch (e) {
reject(e.message);
}
http.get(uri, (res) => {
if (res.statusCode !== 200) {
reject(response.statusCode);
return;
}
res.on('end', () => {
console.log('download end');
});
// 进度、超时等
file.on('finish', () => {
console.log('finish write file')
file.close(resolve);
}).on('error', (err) => {
fs.unlink(dest);
reject(err.message);
})
res.pipe(file);
});
});
}
业务的封装
const {app: electronApp, dialog} = require('electron');
const {createWriteStream} = require('fs');
const {parse} = require('url');
const path = require('path');
const downloadFile = async (app, url) => {
const downloadPath = electronApp.getPath('downloads');
const {pathname} = parse(url);
const fileName = pathname.split('/').pop();
const localFilePath = path(downloadPath, fileName);
const {canceled, filePath} = await dialog.showSaveDialog(app.mainWindow, {
title: '保存附件',
default: localFilePath
})
if (!canceled) {
const savedFilePath = path.join(path.dirname(filePath), fileName);
const writeSteam = createWriteStream(savedFilePath);
const request = app.httpClient.request(url, {
headers: {
'Content-Type': null
},
streaming: true,
followRedirect: true
})
const needShowProgress = Number(request.headers['content-length']) > 1048576;
const downloadResponse = (type) => {
// progress
app.mainWindow.webContents.send('download-progress', {type});
}
request.res.on("data", data => {
writeSteam.write(data);
if (needShowProgress) {
downloadResponse('data');
}
});
request.res.on('end', () => {
writeSteam.end();
downloadResponse('end');
});
request.res.on('error', () => {
downloadResponse('error');
})
}
};
这里涉及到了,日志的本地存储,以及日志的打包上传到服务器。
市面上现在的技术方案
所使用到的插件有winston-daily-rotate-file
,winston-transport
用于文件切割和远程上传。
对于 winston 的封装
const path = require('path');
const winston = require('winston');
require('winston-daily-rotate-file');
const Transport = require('winston-transport');
const {app: electronApp} = require('electron');
const {format} = winston;
const logReomteUrl = 'http://test.com/electron/log';
const logger = function (options = {}) {
return () => {
const logDir = options.logDir || path.join(options.debug ? process.cwd() : electronApp.getPath('userData'), 'logs');
const transportList = [
new winston.transports.DailyRotateFile({
dirname: path.join(logDir, options.name),
filename: `${options.filename || options.name}-%DATE%.log`,
maxSize: '15m',
maxFiles: 7,
createSymlink: true,
symlinkName: `${options.symlinkName || options.name}.log`
}),
new class extends Transport {
constructor(props) {
super(props);
this.options = props;
}
log(options = {}, callback) {
if (process.env.DISABLE_LOG_REMOTE) {
return;
}
const data = {
type: this.options.name,
message: `${options.timestamp} ${options.message}`
};
// 提交服务器端的日志地址。
const url = logReomteUrl;
// request
app.httpClient.request(url, {
method: 'POST',
contentType: "json",
data: data,
disableLogger: true,
}).catch(() => {
});
callback(null, true);
}
}(options)
];
<!--是否同步控制台输出-->
if (process.env.CONSOLE_LOGGER) {
transportList.push(new winston.transports.Console);
}
return new winston.createLogger({
format: format.combine(
format.label({label: options.name}),
format.timestamp({format: "YYYY-MM-DD HH:mm:ss"}),
format.splat(),
format.simple(),
format.printf(({level, timestamp, message, label}) => {
const {tracert = {}, currentUser = {}} = options.currentContext || {};
return [timestamp, level.toUpperCase(), `[${label}]`, tracert.traceId, currentUser.id, message].join("^")
})
),
transports: transportList
})
}
};
// 使用
// electron 桌面端日志
app.electronLog = logger({
name: "electron",
symlinkName: 'app',
debug: app.isDev
})
// 给web端调用的日志
app.webLogger = logger({
name: "web",
debug: app.isDev
});
自行查询。
借助electron 提供的 ipcMain
和ipcRenderer
进行通讯。
// 在主进程中.
const { ipcMain } = require('electron')
ipcMain.on('asynchronous-message', (event, arg) => {
console.log(arg) // prints "ping"
event.reply('asynchronous-reply', 'pong')
})
ipcMain.on('synchronous-message', (event, arg) => {
console.log(arg) // prints "ping"
event.returnValue = 'pong'
})
//在渲染器进程 (网页) 中。
const { ipcRenderer } = require('electron')
console.log(ipcRenderer.sendSync('synchronous-message', 'ping')) // prints "pong"
ipcRenderer.on('asynchronous-reply', (event, arg) => {
console.log(arg) // prints "pong"
})
ipcRenderer.send('asynchronous-message', 'ping')
主要借助 BrowserWindow.webContents.send
和ipcRenderer.on
方法
// 主进程
app.mainWindow.webContents.send('xxx','this is message');
// 渲染window
const {ipcRenderer} = require('electron');
ipcRenderer.on('xxx',(event, message)=>{
console.log(message) // this is message
})
主要是通过 setLoginItemSettings
实现
const {ipcMain, app: electronApp} = require('electron');
// 属性返回启动 Node.js 进程的可执行文件的绝对路径名。
const exePath = process.execPath;
module.exports = (() => {
// 给渲染页面获取当前的状态
ipcMain.handle('get-auto-start-status', () => electronApp.getLoginItemSettings())
// 设置开启自启
ipcMain.on('auto-start-open', () => {
electronApp.setLoginItemSettings({
openAtLogin: true,
path: exePath,
args: []
})
});
//设置开机不自启
ipcMain.on('auto-start-closed', () => {
electronApp.setLoginItemSettings({
openAtLogin: false,
path: exePath,
args: []
})
})
});
主要是借助setAsDefaultProtocolClient
实现其他应用唤起客户端。
const {app: electronApp, dialog} = require('electron');
const path = require('path');
const SCHEMA_NAME = 'xx'; // 协议前缀
if (process.defaultApp) {
if (process.argv.length >= 2) {
electronApp.setAsDefaultProtocolClient(SCHEMA_NAME, process.execPath, [path.resolve(process.argv[1])]);
}
} else {
electronApp.setAsDefaultProtocolClient(SCHEMA_NAME);
}
electronApp.on('open-url', ((event, url) => {
dialog.showErrorBox("Welcome Back", `You arrived from: ${url}`);
}));
借助 globalShortcut
方法实现
const {globalShortcut} = require("electron");
module.exports = (app => {
// 注册全局快捷键
globalShortcut.register("CommandOrControl+Option+Y", () => {
app.mainWindow.show()
})
});
需要考虑window 系统和macOS 系统,对于macOS 还需要考虑是不是暗黑模式。
对于mac 还有
const macLightIcon = path.join(__dirname, "../../../dist-assets/tray/tray@2x.png");
const macDarkIcon = path.join(__dirname, "../../../dist-assets/tray/tray-active@2x.png");
const winLightIcon = path.join(__dirname, "../../../dist-assets/tray/tray-windows.png");
对于mac 端的左上角也有一个应用的菜单,主要是通过 Menu.setApplicationMenu(Menu.buildFromTemplate([]))
实现的
对于 tabs
数组
const tabs = [
{
label: 'Application',
submenu:[
{
label:'xxxx',
accelerator: "CommandOrControl+,",
click:()=>{
//
}
},
{type: "separator"}, // 一根线
{
label:'xxx2',
accelerator: "CommandOrControl+Q",
click:()=>{
//
}
}
]
}
]
默认提供的是中文,然后通过配置文件,来获取到其他语言的翻译
// en-US
module.exports = {
"测试": "Test",
"关于xx": "About xx",
"退出": "Quit",
"调试": "Debug",
"窗口": "Window",
"托盘":'Tray'
};
然后在app 方法里面绑定一个方法
const en = require('../locales/en-US');
// 匹配一个单字字符(字母、数字或者下划线)。等价于 [A-Za-z0-9_]。
const interpolate_reg = /\{(\w*)\}/g;
// replace data
const replaceData = (key, lang) => {
return key.replace(interpolate_reg, value => {
const tempKey = value.slice(1, value.length - 1);
return lang[tempKey] ? lang[tempKey] : key;
})
};
module.exports = (app => {
// 绑定 t 方法。
app.t = ((title, lang = {}) => {
// 绑定到app 上面的语言。
if (app.locale.startsWith('zh')) {
return replaceData(title, lang);
}
const enLang = en[title];
return enLang ? replaceData(enLang, lang) : title;
})
});
然后在调用的时候。
app.t('关于xx') //这样就会根据不同的语言情况。获取到不同翻译了。
然后在启动项目的时候,渲染线程,拉取桌面端的语言,然后根据语言更新window 端的语言。
// 在主线程里面
ipcMain.handle('get-app-config', async () => {
const locale = await app.getLocale();
return {
timestamp: (new Date).getTime(),
locale: locale,
...app.config
}
});
// 在渲染window 里面
import {isElectron, getAppConfig, addEventListener, EVENTS} from "./utils/electron";
async created() {
if (isElectron) {
const appConfig = await getAppConfig();
console.log(appConfig);
//
this.$store.commit('UPDATE_ELECTRON_CONFIG', appConfig);
}
},
主要借助 TouchBar
和TouchBarButton
接口注册进去
每个 TouchBarButton
参数有 label
,backgroundColor
, click
最后通过 win.setTouchBar(TouchBar)
注册进去。
mac版本的的electron 可能会出现花屏现状,解决方案就是关闭硬件加速。
通过 disableHardwareAcceleration
实现。
通过注入 process.env.XXX
这里 XXX 是通过 cross-env
组件注入进去的。
在启动electron的时候
{
"scripts":{
"dev":"cross-env NODE_ENV=development electron ."
}
}
这样就可以在代码里面通过 process.env.NODE_ENV
获取了。
this.isDev = "development" === process.env.NODE_ENV;
const {crashReporter} = require('electron');
crashReporter.start({
productName: "test",
companyName: "test",
submitURL: "https://www.test.com",
autoSubmit: true,
uploadToServer: true,
ignoreSystemCrashHandler: true
});
通过requestSingleInstanceLock
方法,来实现 单例模式
const singleInstanceLock = electronApp.requestSingleInstanceLock();
if (singleInstanceLock) {
//
electronApp.on('second-instance', () => {
// 查看打开的是否是 login window 还是 main window
app.loginWindow &&
!app.loginWindow.isDestroyed() ?
(app.loginWindow.isVisible() || app.loginWindow.show(),
app.loginWindow.isMinimized() && app.loginWindow.restore(),
app.loginWindow.focus()) :
app.mainWindow &&
(app.mainWindow.isVisible() || app.mainWindow.show(),
app.mainWindow.isMinimized() && app.mainWindow.restore(),
app.mainWindow.focus())
});
// 监听 ready 事件
electronApp.on('ready', async () => {
<!--app 的初始化操作-->
await app.init();
<!--唤起登录,自动登录,选择登录。啥的-->
app.launchLogin();
});
} else {
electronApp.quit();
}
在项目启动的时候,需要先用 loading.html 页面加载,防止出现白屏现象。
依赖 BrowserView
和 BrowserWindow
新建完 BrowserWindow 之后,利用BrowserView 加载loading页面,监听BrowserWindow.webContens的dom-ready
事件。移除掉BrowserView
const {BrowserView, BrowserWindow} = require('electron');
const browserWindow = new BrowserWindow({
width: 500,
height: 600,
// 其他参数
});
const loadingBrowserView = new BrowserView();
browserWindow.setBrowserView(loadingBrowserView);
loadingBrowserView.setBounds({
x: 0,
y: 0,
width: 400,
height: 600
});
loadingBrowserView.webContents.loadURL('loading.html');
browserWindow.webContents.on('will-navigate', () => {
browserWindow.setBrowserView(loadingBrowserView);
});
browserWindow.webContents.on('dom-ready', async (event) => {
browserWindow.removeBrowserView(loadingBrowserView);
});
通过在 new BrowserWindow
的时候,传递参数 webPreferences.preload
这样就可以设置electron-bridge了。
在页面运行其他脚本之前预先加载指定的脚本 无论页面是否集成Node, 此脚本都可以访问所有Node API 脚本路径为文件的绝对路径。 当 node integration 关闭时, 预加载的脚本将从全局范围重新引入node的全局引用标志
通过这种方式,就可以实现兼容web端
和桌面端
的应用了。
通过监听webContents.session.webRequest.onBeforeRequest
和 webContents.session.webRequest.onBeforeSendHeaders
就可以了。
// 需要拦截的请求。
const webRequestFilter = {
urls: ["*://test.aaa.com/*", "*://*.ccc.com/*"]
};
browserWindow.webContents.session.webRequest.onBeforeRequest(webRequestFilter, (details, callback) => {
// 监听 before request
// 是否存在下载
if (details.url.includes('desktop-download')) {
downloadFile(app, details.url);
}
// 原始请求被阻止发送或完成,而不是重定向到给定的URL
callback({redirectURL: details.redirectURL});
});
browserWindow.webContents.session.webRequest.onBeforeSendHeaders(webRequestFilter, (details, callback) => {
// 绑定header 头部。
if (details.requestHeaders.Cookie) {
const {ctoken = ''} = cookieParse(details.requestHeaders.Cookie);
if (ctoken) {
details.requestHeaders['x-csrf-token'] = ctoken;
}
}
// When provided, request will be made with these headers.
callback({requestHeaders: details.requestHeaders});
});
主要通过 electron-vue
传送门: github
主要是通过 electron-react-boilerplate
传送门:github
只需要在创建 BrowserWindow
的时候,参数 frame
为false 就可以了。
const { BrowserWindow } = require('electron')
const win = new BrowserWindow({ width: 800, height: 600, frame: false })
win.show()
就需要自定义实现右上角最大、最小、关闭
<header class="drag-area">
<div class="header-actions">
<div @click="handleMinWindow">
<svg t="1586443316286" className="icon-min" viewBox="0 0 1024 1024" version="1.1">
<defs>
<style type="text/css"></style>
</defs>
<path
d="M65.23884 456.152041 958.760137 456.152041l0 111.695918L65.23884 567.847959 65.23884 456.152041z"
p-id="1094"></path>
</svg>
</div>
<div @click="handleUnMaxWindow" v-if="isWinMax">
<svg t="1586445181598" className="icon" viewBox="0 0 1157 1024" version="1.1">
<defs>
<style type="text/css"></style>
</defs>
<path
d="M1086.033752 753.710082 878.220684 753.710082 878.220684 951.774989 878.220684 1021.784509 878.220684 1023.113804 808.211164 1023.113804 808.211164 1021.784509 70.895716 1021.784509 70.895716 1023.113804 0.886196 1023.113804 0.886196 1021.784509 0.886196 951.774989 0.886196 339.413241 0.886196 269.403721 70.895716 269.403721 269.403721 269.403721 269.403721 0.886196 274.277802 0.886196 339.413241 0.886196 1086.033752 0.886196 1151.612289 0.886196 1156.043271 0.886196 1156.043271 683.700563 1156.043271 753.710082 1086.033752 753.710082ZM70.895716 951.774989 808.211164 951.774989 808.211164 753.710082 808.211164 683.700563 808.211164 339.413241 70.895716 339.413241 70.895716 951.774989ZM1086.033752 70.895716 339.413241 70.895716 339.413241 269.403721 808.211164 269.403721 878.220684 269.403721 878.220684 339.413241 878.220684 683.700563 1086.033752 683.700563 1086.033752 70.895716Z"
p-id="2415"></path>
</svg>
</div>
<div @click="handleMaxWindow" v-else>
<svg t="1586443335243" className="icon-max" viewBox="0 0 1024 1024" version="1.1">
<defs>
<style type="text/css"></style>
</defs>
<path
d="M128.576377 895.420553 128.576377 128.578424l766.846222 0 0 766.842129L128.576377 895.420553zM799.567461 224.434585 224.432539 224.434585l0 575.134923 575.134923 0L799.567461 224.434585z"
p-id="1340"></path>
</svg>
</div>
<div @click="handleCloseWindow">
<svg t="1586443316286" className="icon-close" viewBox="0 0 1024 1024"
version="1.1">
<path
d="M895.423623 224.432539 607.855138 512l286.901289 297.699216 0.666172 85.723384-95.856162 0L512 607.856162 224.432539 895.423623l-95.856162 0 0-95.856162 287.567461-287.567461L128.576377 224.432539l0-95.856162 95.856162 0 287.567461 287.567461 287.567461-287.567461 95.856162 0L895.423623 224.432539z"
p-id="1217"></path>
</svg>
</div>
</div>
</header>
.drag-area {
-webkit-app-region: drag;
-webkit-user-select: none;
user-select: none;
z-index: 500;
width: 100vw;
}
顶部需要设置一条可以拖拽的区域
.drag-area {
-webkit-app-region: drag;
-webkit-user-select: none;
user-select: none;
z-index: 500;
width: 100vw;
background-color: transparent;
height: 18px;
position: fixed;
}
.drag-area {
-webkit-app-region: drag; // 支持窗口拖拽
-webkit-user-select: none;
user-select: none;
z-index: 500;
}
待定。。。
主要的打包方案 如下:
通过在 package.json 里面配置
{
"scripts":{
"build":"electron-builder"
}
"build":{
"productName":"productName",
"appId":"appId",
"directories":{
"output": "output"
},
"files":[
],
"nsis":{
},
"dmg":{
},
"mac":{
},
"win":{
},
"linux":{
}
}
}
打包参数:
electron-packager <sourcedir> <appname> <platform> <architecture> <electron version> <optional options>
1、支持平台有:Windows (32/64 bit)、OS X (also known as macOS)、Linux (x86/x86_64);
2、进行应用更新时,使用electron内置的autoUpdate进行更新
3、支持CLI和JS API两种使用方式;
为此,我将业务系统里面用到的各个客户端需要的功能,剥离了出来,新建了一个template 方便新业务系统的开发。
编码不易,欢迎star
https://github.com/bosscheng/electron-app-template