十分鐘實現本身的Typora

本章主要內容: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內容
  • 將Markdown內容呈現爲HTML
  • 將生成的HTML保存到文件系統中
  • 將生成的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
複製代碼

項目的各個部分是

  • index.html-包含全部爲UI提供結構的HTML標記
  • main.js-包含咱們的主進程的代碼
  • renderer.js-包含UI的全部交互代碼
  • style.css-包含樣式的CSS
  • package.json-包含全部依賴項,並在啓動主進程時將Electron指向main.js

爲了簡單起見,除了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應用程序

在理想的世界中,咱們在編寫代碼時永遠不會出錯。

接口和方法永遠不會在不一樣的版本之間更改,並且您的做者沒必要每次發佈本書中應用程序使用的依賴項的新版本時都屏住呼吸。

咱們並不生活在那個世界上。所以,咱們可使用開發工具幫助咱們跟蹤並有望消除缺陷。

調試渲染器進程

到目前爲止,一切都進行得至關順利,但可能不久以後咱們就必須調試一些棘手的狀況。由於Electron應用程序是基於Chrome的,因此咱們在構建Electron應用程序時可使用Chrome開發者工具就不足爲奇了(圖3.9)。

調試渲染器過程相對簡單。Electron的默認應用程序菜單提供了一個命令來打開應用程序中的Chrome開發工具。在第6章中,咱們將學習如何建立咱們本身的自定義菜單,並在您不但願將其公開給用戶的狀況下消除此功能。

還有另外兩種訪問開發人員工具的方法。

在任什麼時候候,您均可以按macOS上的Command-Option-IWindowsLinux上的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 的調試器協議信息。 默認的port5858

electron --inspect=5858 your/appCopy
複製代碼

使用VSCode進行主進程調試

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。

總結

  • 在接下來的幾章中,咱們將製作一個markdown到html編輯器。
  • Flexbox受到現代瀏覽器的支持,容許咱們輕鬆地實現一個雙窗格界面,當用戶改變窗口的大小時,這個界面將進行調整。
  • Chrome開發工具在全部渲染器進程中均可用,能夠從默認的電子應用程序、鍵盤快捷鍵或主進程觸發。
  • 此時Electron中尚未徹底支持Node Inspector檢查器。
  • Visual Studio代碼提供了一組豐富的工具,用於調試應用程序主進程中的問題。
相關文章
相關標籤/搜索