本章主要内容:javascript
- 介绍咱们将在接下来的几章中构建的应用程序
- 配置咱们的CSS样式表,使其看起来更像一个本机应用程序
- 回顾在Electron中主进程和渲染器进程之间的关系
- 为咱们的主进程和渲染器进程实现基本功能
- 在Electron渲染进程中访问Chrome开发者工具
咱们的书签管理器是一个很好的开始,但它只触及了咱们能够用Electron作什么。css
在本章中,咱们将更深刻地探讨,并为与用户操做系统创建更紧密联系的应用程序打下基础。在接下来的几章中,咱们将实现触发操做系统用户界面,对文件系统进行读写和访问剪贴板的功能。html
咱们正在构建一个简单的Markdown编辑器,它容许咱们建立新的或打开现有的Markdown文件,将它们转换为HTML,并将HTML保存到文件系统和剪贴板中。让咱们把这个应用程序称为Fire Sale,由于它毕竟是一个廉价编辑器,只是稍微聪明一点而已。java
在本章的最后,咱们将讨论在出现问题时调试Electron应用程序的技术和工具。node
让咱们从为咱们不起眼的小应用程序设置目标开始。git
对于桌面应用程序,咱们的许多特性可能看起来有些平庸,这就是重点。它们是桌面应用程序的标准配置,但彻底超出了传统web应用程序的能力范围,传统web应用程序没法访问独立浏览器选项卡以外的任何内容。github
咱们的应用程序将由两个窗格组成,用户能够编写或编辑Markdown和一个右窗格,该窗格以HTML形式呈现用户的Markdown。在顶部有一系列按钮,容许用户从文件系统加载文本文件,并将结果写入剪贴板或文件系统。web
在应用程序的第一阶段,咱们构建了如下的界面。在图3.1。咱们还能够向效果图(以及随后的应用程序)添加额外的用户界面元素,但这是一个很好的开始。shell
图3.1 咱们的应用程序的线框显示,用户能够在左侧窗格中输入文本,或者从用户的文件系统的文件中加载文本。npm
在这一章中,咱们为咱们的应用奠基了基础。咱们建立项目的结构,安装依赖项,设置主进程和呈现器进程,构建用户界面,并在用户向左侧窗格输入文本时实现markdown到HTML的转换。
咱们将在接下来的几章中分阶段构建应用程序的其他部分。在每一章中,您将下载咱们应用程序的当预期目标代码。经过这种方式,您能够切换到一个章节,其中包含您感兴趣的功能,而没必要从头构建整个应用程序。
在第一阶段,咱们的应用程序将可以
在后面的章节中,咱们的应用程序使用本地操做系统接口跟踪最近打开的文档。咱们能够将Markdown文件从Finder或Windows资源管理器拖放到应用程序上,并让应用程序当即打开该Markdown文件。当咱们右键单击应用程序的不一样区域时,应用程序将有本身的自定义应用程序菜单和自定义上下文菜单。
咱们还利用了操做系统特有的特性,好比更新应用程序的标题栏,以显示当前打开的文件,以及自上次保存以来是否已经更改。若是计算机上的其余应用程序在打开文件时更改了文件,咱们还实现了其余功能,好比更新应用程序中的内容。
如图3.2所示的文件结构与咱们在前一章中商定并用于书签管理器的结构很是类似。
为了简化和清晰,在咱们继续熟悉Electron时,咱们在app/main.js
中保存了主进程的全部代码,在app/renderer.js
中保存了单渲染器进程的全部代码。咱们将app文件夹存储在基于unix的操做系统上,以便可以快速生成它,以下面的清单所示。或者,您能够在GitHub上查看这个项目的主分支,网址是https://github.com/sanshengshui/AUG。
图3.2 咱们工程结构
列表3.1 生成应用文件结构
mkdir app && touch app/index.html app/main.js app/renderer.js app/style.css
复制代码
项目的各个部分是
为了简单起见,除了Electron以外,咱们还从两个依赖项开始做为运行时。咱们使用一个名为marked的库来处理Markdown到HTML转换的繁重工做。
对于这个项目,经过运行npm init --yes生成一个package.json
。--yes标记容许您跳过前一章中的提示。生成package.json以后,运行如下命令安装必要的依赖项:
npm install electron marked --save
复制代码
图3.3 Electron首先寻找咱们的主进程,它负责生成一个或多个渲染器进程,其负责显示咱们的UI。
在咱们package.json的main条目被配置为加载index.js做为应用程序的主进程。如图3.3所示,咱们须要将其调整为app/main.js
。咱们还须要一个渲染器进程,为用户提供应用程序的界面。在app/main.js中,让咱们添加以下代码。
列表3.2 引导主进程: ./app/main.js
const{ app, BrowserWindow } = require('electron')
//在顶层声明mainWindow,以便在“ready”事件完成后不会将其回收为垃圾
let mainWindow = null;
app.on('ready', () => {
//使用默认属性建立一个新的BrowserWindow
mainWindow = new BrowserWindow({
webPreferences: {
// webPreferences中的nodeIntegrationInWorker选项设置为true,Electron5.x之后,缺省为false
nodeIntegration: true
}
})
//在刚才建立的BrowserWindow实例中加载app/index.html
mainWindow.loadFile('app/index.html');
mainWindow.on('closed', () => {
//在窗口关闭时将进程设置为null
mainWindow = null;
});
});
复制代码
这足以启动咱们的应用程序。也就是说,因为咱们的主进程目前在渲染器进程中加载了一个空文件,因此没有发生太多事情。
在Electron中要得到图3.1中效果图的可行版本,实现必要的HTML和CSS是至关容易的。由于咱们只须要支持一个浏览器,而这个浏览器支持web平台提供的最新和最强大的特性,如图3.4所示。
图3.4 主进程将建立一个渲染器程序进程并告诉它加载index.html。而后,它将像在浏览器中同样加载CSS和JavaScript。
在index.html,咱们添加清单3.3中的标记来建立图3.5中的浏览器窗口。
图3.5 开始咱们第一个未样式化的Electron应用
列表3.3 咱们应用的标记:./app/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Fire Sale</title>
<link rel="stylesheet" href="style.css" type="text/css">
</head>
<body>
<!--控件部分在顶部添加了用于打开和保存文件的按钮。稍后咱们将向这些按钮添加功能。-->
<section class="controls">
<button id="new-file">New File</button>
<button id="open-file">Open File</button>
<button id="save-markdown" disabled>Save File</button>
<button id="revert" disabled>Revert</button>
<button id="save-html">Save HTML</button>
<button id="show-file" disabled>Show File</button>
<button id="open-in-default" disabled>Open in Default Application</button>
</section>
<!--咱们的应用程序容许使用.raw-markdown类编写和编辑文本区域中的内容,并使用.rendered-html类在div元素中呈现该内容。-->
<section class="content">
<!--<label>标签是可选的,而且包含了这些标签,以使视障用户更容易访问应用程序。 -->
<label for="markdown" hidden>Markdown Content</label>
<textarea class="raw-markdown" id="markdown"></textarea>
<div class="rendered-html" id="html"></div>
</section>
</body>
<!--在文件末尾的标记中,咱们须要渲染进程的代码,它位于同一个目录中的renderer.js中。 -->
<script> require('./renderer'); </script>
</html>
复制代码
咱们的应用程序目前尚未太多须要查看的地方。
若是您和我同样,您对我在效果图中引入的两列接口有点怀疑。在讨论如何使用HTML和CSS实现列时,不多使用easy这个词。
幸运的是,咱们能够自信地使用添加到CSS3的名为Flexbox的新布局模式来快速定义应用程序的两列布局。Flexbox使建立页面布局变得很容易,能够在各类屏幕大小范围内进行可预测的操做,如清单3.4所示。它对CSS来讲是相对较新的,直到最近才获得Internet Explorer的支持。
正如咱们在第1章和第2章中讨论的,咱们的应用程序老是跟上Chrome的最新版本,因此咱们能够放心地使用Flexbox布局模式,而不用担忧跨浏览器兼容性。
使用Flexbox建立页面布局:./app/style.css
/*选择一个更新的CSS框模型,它将正确地设置元素的宽度和高度*/
html {
box-sizing: border-box;
}
/* 将此设置传递给页面上的全部其余元素和伪元素*/
*, *:before, *:after {
box-sizing: inherit;
}
html, body {
height: 100%;
width: 100%;
overflow: hidden;
}
body {
margin: 0;
padding: 0;
position: absolute;
}
/* 在整个应用程序中使用操做系统的默认字体 */
body, input {
font: menu;
}
/*移除浏览器围绕活动输入字段的默认突出显示*/
textarea, input, div, button {
outline: none;
margin: 0;
}
.controls {
background-color: rgb(217, 241, 238);
padding: 10px 10px 10px 10px;
}
button {
font-size: 14px;
background-color: rgb(181, 220, 216);
border: none;
padding: 0.5em 1em;
}
button:hover {
background-color: rgb(156, 198, 192);
}
button:active {
background-color: rgb(144, 182, 177);
}
button:disabled {
background-color: rgb(196, 204, 202);
}
.container {
display: flex;
flex-direction: column;
min-height: 100vh;
min-width: 100vw;
position: relative;
}
/* 使用Flexbox对齐应用程序的两个窗格*/
.content {
height: 100vh;
display: flex;
}
/* 使用Flexbox将两个窗格设置为相同的宽度 */
.raw-markdown, .rendered-html {
min-height: 100%;
max-width: 50%;
flex-grow: 1;
padding: 1em;
overflow: scroll;
font-size: 16px;
}
.raw-markdown {
border: 5px solid rgb(238, 252, 250);;
background-color: rgb(238, 252, 250);
font-family: monospace;
}
复制代码
样式表有两个主要目标。首先,咱们想利用像Flexbox这样的现代CSS特性来设计咱们的UI。其次,咱们但愿采起一些小步骤,使应用程序的外观和感受更像一个真实的web应用程序(参见图3.6)。
图3.6 咱们的应用程序已经使用CSS的现代特性给出了一些基本的样式。
box-sizing
属性在CSS中处理一个历史上的奇怪现象,在一个宽度为200像素的元素中添加50个像素的填充将致使它的宽度为300像素(每边添加50个像素的填充),对于边框也是同样。
当box-sizing
被设置为border-box时,咱们的元素会考虑到咱们设置它们的高度和宽度。总的来讲,这是一件好事。在这个CSS规则中,咱们还让全部其余元素和伪元素都尊重咱们经过将box-sizing设置为border-box所作的艰苦工做。
咱们但愿咱们的应用程序可以适应本地应用程序。朝着这个方向迈出的重要一步是使用全部其余应用程序都使用的系统字体。例如,尽管macOS在整个操做系统中使用San Francisco做为默认字体,但它不能做为常规字体使用。咱们将font属性设置为menu,它依赖于操做系统来使用它的默认字体——即便咱们没法访问它。
浏览器在当前活动的UI元素周围设置一个边框。在macOS中,这个边框是蓝色的辉光。您可能从未过多地考虑过它,由于咱们已经习惯了在web上使用它,可是当咱们开发桌面应用程序时,它看起来并不合适。在咱们的应用程序中,它看起来尤为糟糕,其中一半的UI其实是一个大型文本输入。经过将outline
设置为none,咱们删除了活动元素周围的非天然辉光。
在.content
、.raw-markdown
和.rendered-html
规则中,咱们实现了一个简单的Flexbox布局,这将使咱们的应用程序看起来更像咱们在图3.1中介绍的效果。content类的元素将包含咱们的两列。咱们将display属性设置为flex,以使用前面讨论的Flexbox技术。下一步,咱们设置flex- growth,它指定flex项的增加因子, 固然能够。把它看做元素的尺度相对于它的兄弟元素多是有帮助的。在本例中,咱们使用Flexbox将两列设置为相等的比例。
若是你仔细观察你的应用程序的启动,您将注意到,在Electron加载index.html并在窗口中呈现DOM以前,窗口彻底为空。用户不习惯在本地应用程序中看到这种状况,咱们能够经过从新思考如何启动窗口来避免这种状况。
若是您认为应用程序第一次启动时的虚无闪光是无心义的,考虑主进程中的代码:它建立一个窗口,而后在其中加载内容。若是咱们隐藏窗口直到内容被加载呢?而后,当UI准备好时,咱们显示窗口,并避免短暂地暴露一个空窗口。
列表3.5 当DOM就绪时优雅地显示窗口
app.on('ready', () => {
//使用默认属性建立一个新的BrowserWindow
mainWindow = new BrowserWindow({
show: false,
webPreferences: {
// webPreferences中的nodeIntegrationInWorker选项设置为true,Electron5.x之后,缺省为false
nodeIntegration: true
}
})
//在刚才建立的BrowserWindow实例中加载app/index.html
mainWindow.loadFile('app/index.html');
mainWindow.once('ready-to-show', () => {
//当DOM就绪时显示窗口。
mainWindow.show();
});
mainWindow.on('closed', () => {
//在窗口关闭时将进程设置为null
mainWindow = null;
});
});
复制代码
咱们将一个对象传递给BrowserWindow构造函数,默认状况下将其设置为hidden
。当BrowserWindow实例触发它的“ready-to-show”事件时,咱们将调用它的show()方法,这将在UI彻底准备好运行后使它再也不隐藏。当应用程序经过网络加载远程资源时,这种方法甚至更有用,由于初始化页面可能须要更长的时间。
让咱们把一些基本功能放在适当的位置上。对于初学者,咱们但愿在左窗格中的Markdown发生更改时更新右窗格中呈现的HTML视图(参见图3.7)。这就是咱们惟一的依赖—Marked—发挥做用的地方。
图3.7 咱们将在左侧窗格中添加一个事件监听器,它将以HTML的形式呈现标记并显示在右侧窗格中。
引入依赖项很容易,由于咱们可使用Node的require
来引入marked。让咱们在app/renderer.js中添加如下内容。
列表3.6 引入依赖: ./app/renderer.js
const marked = require('marked');
复制代码
如今,咱们能够经过变量marked使用Marked。鉴于咱们在图3.7中讨论了应用程序的功能,您可能已经开始怀疑,在开发应用程序时,咱们将大量使用#markdown文本区域和#html元素。让咱们使用一对变量来存储对每一个元素的引用,以便更容易地使用它们,如清单3.7所示。在此过程当中,咱们还将为UI顶部的每一个按钮建立变量。
列表3.7 缓存DOM选择器: ./app/renderer.js
const markdownView = document.querySelector('#markdown');
const htmlView = document.querySelector('#html');
const newFileButton = document.querySelector('#new-file');
const openFileButton = document.querySelector('#open-file');
const saveMarkdownButton = document.querySelector('#save-markdown');
const revertButton = document.querySelector('#revert');
const saveHtmlButton = document.querySelector('#save-html');
const showFileButton = document.querySelector('#show-file');
const openInDefaultButton = document.querySelector('#open-in-default');
复制代码
咱们还至关频繁地在htmlView中呈现Markdown,因此咱们想给本身一个函数,以便未来更容易实现。
列表3.8 转换markdown到HTML: ./app/renderer.js
marked将咱们要呈现的Markdown内容做为第一个参数,并将选项的对象做为第二个参数。咱们但愿避免意外的脚本注入,所以咱们传入了一个对象,并将sanitize属性设置为true。
最后,咱们向markdownView添加了一个事件监听器,它将在keyup上读取它的内容(在textarea元素中,内容存储在它的value属性中),经过marked运行它们,而后将它们加载到htmlView中。结果如图3.8所示。
列表3.9 当Markdown更改时从新呈现HTML: ./app/renderer.js
markdownView.addEventListener('keyup', (event) => {
const currentContent = event.target.value;
renderMarkdownToHtml(currentContent);
});
复制代码
图3.8 咱们的应用程序接受用户在左窗格中键入的内容,并在右窗格中将其自动呈现为HTML。该内容由用户提供,不属于咱们的应用程序。
基本功能已经就绪,咱们准备开始研究只有在Electron应用程序中才可能实现的特性,首先从文件系统中读写文件开始。当全部这些都完成后,应用程序的呈现程序流程应该是这样的。
列表3.10 渲染进程: ./app/renderer.js
const marked = require('marked');
const markdownView = document.querySelector('#markdown');
const htmlView = document.querySelector('#html');
const newFileButton = document.querySelector('#new-file');
const openFileButton = document.querySelector('#open-file');
const saveMarkdownButton = document.querySelector('#save-markdown');
const revertButton = document.querySelector('#revert');
const saveHtmlButton = document.querySelector('#save-html');
const showFileButton = document.querySelector('#show-file');
const openInDefaultButton = document.querySelector('#open-in-default');
const renderMarkdownToHtml = (markdown) => {
htmlView.innerHTML = marked(markdown, { sanitize: true });
};
markdownView.addEventListener('keyup', (event) => {
const currentContent = event.target.value;
renderMarkdownToHtml(currentContent);
});
复制代码
在理想的世界中,咱们在编写代码时永远不会出错。
接口和方法永远不会在不一样的版本之间更改,并且您的做者没必要每次发布本书中应用程序使用的依赖项的新版本时都屏住呼吸。
咱们并不生活在那个世界上。所以,咱们可使用开发工具帮助咱们跟踪并有望消除缺陷。
到目前为止,一切都进行得至关顺利,但可能不久以后咱们就必须调试一些棘手的状况。由于Electron应用程序是基于Chrome的,因此咱们在构建Electron应用程序时可使用Chrome开发者工具就不足为奇了(图3.9)。
调试渲染器过程相对简单。Electron的默认应用程序菜单提供了一个命令来打开应用程序中的Chrome开发工具。在第6章中,咱们将学习如何建立咱们本身的自定义菜单,并在您不但愿将其公开给用户的状况下消除此功能。
还有另外两种访问开发人员工具的方法。
在任什么时候候,您均可以按macOS上的Command-Option-I
或Windows或Linux上的Control-Shift-I
打开工具(图3.10)。此外,您还能够经过编程方式触发开发人员工具。
BrowserWindow实例上的webcontent属性有一个名为openDevTools()
的方法。如清单3.11所示,这个方法将在调用它的BrowserWindow中打开开发工具。
图3.9 Chrome开发工具在渲染器过程当中可用,就像在基于浏览器的应用程序中同样。
图3.10 该工具能够在Electron提供的默认菜单中开或关。您还可使用Windows上的Control-Shift-I或macOS上的Command-Option-I来触发它们。
列表3.11 从主流程打开开发者工具: ./app/main.js
app.on('ready', () => {
mainWindow = new BrowserWindow({
show: false,
webPreferences: {
nodeIntegration: true
}
});
mainWindow.loadFile(`app/index.html`);
mainWindow.once('ready-to-show', () => {
mainWindow.show();
mainWindow.webContents.openDevTools(); //咱们能够经过编程方式在主窗口加载开发工具时当即打开它。
});
mainWindow.on('closed', () => {
mainWindow = null;
});
});
复制代码
调试主进程并不容易。Node Inspector是调试Node.js应用程序的经常使用工具,为了提供一个能够调试主进程的方法,Electron 提供了 --inspect
开关。使用以下的命令行开关来调试 Electron 的主进程:--insepct=[port]
当这个开关用于 Electron 时,它将会监听 V8 引擎中有关 port
的调试器协议信息。 默认的port
是 5858
。
electron --inspect=5858 your/appCopy
复制代码
Visual Studio Code是一个免费的开放源码的IDE,适用于Windows、Linux和macOS,而且是由Microsoft在Electron之上构建的。Visual Studio Code提供了一组用于调试节点应用程序的丰富工具,这使得调试Electron应用程序比前面提到的要容易得多。
设置构建任务的一种快速方法是让Visual Studio Code在没有构建任务的状况下构建应用程序。 在Windows上按Control-Shift-B
或在macOS上按Command-Shift-B
,将提示您建立一个构建任务,如图3.11所示。
图3.11 在没有适当的构建任务的状况下触发构建任务,Visual Studio Code将提示为您建立一个。
列表3.12 在Windows的Visual Studio Code中设置构建任务: task.json
{
// 有关 tasks.json 格式的文档,请参见
// https://go.microsoft.com/fwlink/?LinkId=733558
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "start",
"problemMatcher": []
}
]
}
复制代码
如今,当您按下Windows上的Control-Shift-B
或macOS上的Command-Shift-B
时,您的电子应用程序将启动。这不只对于在Visual Studio Code中设置调试很是重要,并且一般也是启动应用程序的一种方便方法。下一步是设置Visual Studio Code来启动应用程序,并将其链接到其内置调试器(图3.12)。
要建立启动任务,请转到上面的终端选项卡,并单击配置默认生成任务。Visual Studio Code将询问您想要建立哪一种配置文件。选择Node并用清单3.13替换文件的内容。
图3.12 在Debug选项卡中,单击gear, Visual Studio Code将建立一个配置文件,用于表明您启动调试器。
列表3.13 为Windows的Visual Studio代码设置启动任务
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Main Process",
"type": "node",
"request": "launch",
"cwd": "${workspaceRoot}",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
},
"args" : ["."],
"outputCapture": "std"
}
]
}
复制代码
有了这个配置文件,您能够单击主进程中任何一行的左边缘来设置断点,而后按F5运行应用程序。 执行将在断点处暂停,容许您检查调用堆栈,肯定范围内的变量,并与活动控制台进行交互。断点并非调试代码的惟一方法。 您还能够监视特定的表达式,或者在抛出未捕获异常时将其放入调试器(图3.13)。
图3.13 内置在Visual Studio Code中的调试器容许您暂停应用程序的执行,并顺便检查bug。
您极可能没有使用Visual Studio Code。这很好。这并非本书的先决条件,使用您最熟悉的文本编辑器或IDE几乎确定没问题。 此外,Visual Studio Code并非惟一支持调试主进程。例如,您能够在这里找到配置WebStorm的详细信息:mng.bz/Y5T6。
Node Inspector
检查器。