警告:阅读下面内容前请详细阅读

下面的内容是我在尝试构建RailgunMediaEncoder中尝试使用的方法, 我无法保证下面的方法绝对符合"最佳实践".
USE IT AT YOUR OWN RISK.
如果有任何问题及建议,欢迎提出.

0. 开始之前

一个标准的Electron的应用由两部分组成: 用于在后台运行的主线程项目和在前台运行的ui线程项目. 这两部分是相对独立的, 其间的通讯只能通过ipc(线程间通讯)完成. 在开发时, 这两部分也应该作为两个独立的项目来管理.
这里我们主线程使用纯TypeScript完成主线程项目, 使用Angular + TypeScript完成ui线程项目.
下面的例子中我们希望的源代码目录结构:
Project Structure

1. 创建Angular项目

首先确保你已经全局安装了Angular CLI,如果没有, 使用下面的命令安装:

1
npm install -g @angular/cli

安装后,首先使用以下命令来创建一个空白项目(不包含app)

1
ng new --createApplication=false --newProjectRoot="./" HelloElectron

完成后,建议使用vscode打开项目目录(这里为HelloElectron)来继续我们的下一步操作.

1
2
cd HelloElectron
code .

继续执行以下命令来创建前端应用

1
ng generate app ui

在 cli 询问 ? Would you like to add Angular routing? (y/N)时, 输入y来添加路由, 其它选项按需选择.

以上命令跑完后, 运行ng serve --open 应该可以在浏览器中看到angular的hello world页面.
Hello world

接下来,打开angular.json修改生成设置.

  1. 修改outputPathdist/app/ui
  2. 修改outputHashingnone

打开ui/src/index.html, 修改base href:

1
2
3
<base href="/">
修改为:
<base href="./">

2.配置Electron

安装以下必备库

  • Electron npm install --save-dev electron
  • ngx-electron npm install --save ngx-electron
  • @types/node npm install --save-dev @types/node
  • electron-builder (用于打包electron应用, 若手动打包可不安装) npm install --save-dev electron-builder

PS: 如果你深受The Girl Friend Wall困扰的话, 安装Electron时post-install部分可能会卡非常非常非常久(npm需要下载90MB左右的Electron包). 此时可以参考http-proxy设置代理.

3. 建立主线程项目

首先, 创建目录core/ , 在之中创建tsconfig.json, 内容参考如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"compileOnSave": false,
"compilerOptions": {
"module": "commonjs",
"target": "es2015",
"sourceMap": true,
"outDir": "../dist/app",
"typeRoots": [
"../node_modules/@types"
],
"moduleResolution": "node",
"experimentalDecorators": true
}
}

创建tslint.json,配置可自行按需设定,或直接复制ui/tslint.json中的内容.

创建index.ts作为整个Electron应用的入口点, 内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import * as path from 'path';
import {app, BrowserWindow} from 'electron';


let mainWindow: BrowserWindow = null;
// 单进程锁, 按需添加
function singleInstance() {
app.requestSingleInstanceLock();

app.on('second-instance', () => {
if (mainWindow) {
if (mainWindow.isMinimized()) {
mainWindow.restore();
}
mainWindow.focus();
}
});
}

// 创建主窗口
function createWindow() {
mainWindow = new BrowserWindow({
width: 1080,
minWidth: 680,
height: 840,
frame: false,
title: app.name,
webPreferences: {
nodeIntegration: true,
experimentalFeatures: true
}
});

mainWindow.loadURL(path.join('file://', __dirname, 'ui/index.html'));
mainWindow.webContents.on('will-navigate', (event, url) => {
if (url !== path.join('file://', __dirname, 'ui/index.html')) {
event.preventDefault();
mainWindow.loadURL(path.join('file://', __dirname, 'ui/index.html'));
}
});
}

function init() {
singleInstance();
app.on('ready', () => {
createWindow();
// 创建窗口后立即打开DevTools, 按需选择.
//mainWindow.webContents.openDevTools();
});

app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});

app.on('activate', () => {
if (mainWindow === null) {
createWindow();
}
});
}

init();

4. 配置生成脚本

打开package.json, 将script节替换为以下内容:

1
2
3
4
5
6
7
"scripts": {
"build": "tsc -p core && ng build",
"build-prod": "tsc -p core && ng build --prod",
"start": "npm run build && electron ./dist/app/index.js",
"clean": "rm ./dist -rf",
"lint": "echo Linting UI && ng lint && echo Linting Core... && tslint -p core"
},

5. 配置自动打包(可选)

首先请确保你已经安装了electron-builder 在package.json中添加build

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
"build": {
"appId": "net.example.HelloElectron",
"productName": "Hello Electron",
"directories": {
"app": "dist/app",
"output": "dist/pack",
},
"nsis": {
"oneClick": false,
"perMachine": true,
"allowToChangeInstallationDirectory": true,
"license": "LICENSE",
"artifactName": "${productName}.Setup.${version}.${ext}"
},
"win": {
"target": [
{
"target": "nsis"
},
{
"target": "zip"
}
]
},
"mac": {
"target": "dmg"
},
"linux": {
"target": [
{
"target": "deb"
},
{
"target": "rpm"
},
{
"target": "tar.gz"
}
]
}
},

有关以上json的更多信息,可参见Configuration.

然后在script节添加publish:

1
"publish": "cp package.release.json dist/app/package.json && electron-builder",

接下来,创建package.build.json, 内容参考如下

1
2
3
4
5
6
7
8
9
10
11
{
"name": "HelloElectron",
"version": "1.0.0",
"author": "EdgeNeko",
"homepage": "https://example.com",
"private": true,
"license": "MIT",
"dependencies": {
}
}

dependencies中,添加需要打包到最终应用的node库. 有关双package.json结构, 可参考此文档.

运行以下命令即可打包:

1
2
3
npm run clean
npm run build-prod
npm run publish

打包结果会保存在dist/pack

6. 示例:在ui线程调用Node

创建一个服务,名为NodeService, 内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import {Injectable} from '@angular/core';
import fs from 'fs';
import {ElectronService} from 'ngx-electron';

@Injectable({
providedIn: 'root'
})
export class NodeService {

public fs: typeof fs;
public childProcess: typeof childProcess;

constructor(
electronService: ElectronService
) {
this.fs = electronService.remote.require('fs');
}
}

接下来,就可以通过注入NodeService并以nodeService.fs来调用node中的fs了.

7. 配置调试

这部分目前我还没有什么好的解决方案,如果米娜桑有解决方案欢迎在下面提出QAQ