乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      第二章 你第首個Electron應用 | Electron in Action(中譯)

       看見就非常 2020-05-25

      本章主要內(nèi)容

      • 構建并啟動Electron應用

      • 生成package.json,配置成Electron應用

      • 在你的項目中包含預先構建Electron版本

      • 配置package.json以啟動主進程

      • 從主進程生成渲染進程

      • 利用Electron限制寬松的優(yōu)點構建通常在瀏覽器無法構建的功能

      • 使用Electron的內(nèi)置模塊來回避一些常見的問題

      在第一章中,我們從高的層次上,討論了什么是Electron。說到底這本書叫做《Electron實戰(zhàn)》,對吧?在本章中,我們通過從頭開始設置和構建一個簡單的應用程序來管理書簽列表,從而學習Electron的基本知識。該應用程序將利用只有在現(xiàn)代的瀏覽器中才能使用的特性。

      在上一章的高層次討論中,我提到了Electron是一個類似于Node的運行時。這仍然是正確的,但是我想回顧下這一點。Electron不是一個框架——它不提供任何框架,也沒有關于如何構造應用程序或命名文件的嚴格規(guī)則,這些選擇都留給了我們這些開發(fā)者。好的一面是,它也不強制執(zhí)行任何約定,而且在入手之前,我們不需要多少概念上的樣板信息去學習。

       

      構建書簽列表應用程序

       

      讓我們從構建一個簡單而又有些幼稚的Electron應用程序開始,來加強我們已經(jīng)介紹過的所有內(nèi)容的理解。我們的應用程序接受url。當用戶提供URL時,我們獲取URL引用的頁面的標題,并將其保存在應用程序的localStorage中。最后,顯示應用程序中的所有鏈接。您可以在GitHub上找到本章的完整源代碼(https://github.com/electron-in-action/bookmarker)。

        在此過程中,我們將指出構建Electron應用程序的一些優(yōu)點,例如,可以繞過對服務器的需求,使用最前沿的web api,這些web api并不廣泛支持所有瀏覽器,因為這些APIs是在現(xiàn)代版本的Chromium中實現(xiàn)。圖2.1是我們在本章構建的應用程序的效果圖。

      圖2.1 我們在本章中構建的應用程序效果圖

       

        當用戶希望將網(wǎng)站URL保存并添加到輸入字段下面的列表中時,應用程序向網(wǎng)站發(fā)送一個請求來獲取標記。成功接收到標記后,應用程序獲取網(wǎng)站的標題,并將標題和URL添加到網(wǎng)站列表中,該列表存儲在瀏覽器的localStorage中。當應用程序啟動時,它從localStorage讀取并恢復列表。我們添加了一個帶有命令的按鈕來清除localStorage,以防出現(xiàn)錯誤。因為這個簡單的應用程序旨在幫助您熟悉Electron,所以我們不會執(zhí)行高級操作,比如從列表中刪除單個網(wǎng)站。

       

      搭建Electron應用

      1. npm init 生成package.json

      2. 搭建Electron目錄框架

       

      應用程序結構的定義取決于您的團隊或個人處理應用程序的方式。許多開發(fā)人員采用的方法略有不同。觀察學習一些更成熟的電子應用程序,我們可以辨別出共同的模式,并在本書中決定如何處理我們的應用程序。

      出于我們的目的,為了讓本書文件結構達成一致。做出一下規(guī)定,我們有一個應用程序目錄,其中存儲了所有的應用程序代碼。我們還有一個package.json將存儲依賴項列表、關于應用程序的元數(shù)據(jù)和腳本,并聲明Electron應該在何處查找主進程。在安裝了依賴項之后,最終會得到一個由Electron為我們創(chuàng)建的node_modules目錄,但是我們不會在初始設置中包含它

      就文件而言,讓我們從應用程序中的兩個文件開始:main.jsrenderer.js。它們是帶有標識的文件名,因此我們可以跟蹤這兩種類型的進程。我們在本書中構建的所有應用程序的開始大致遵循圖2.2中所示的目錄結構。(如果你在運行macOS,你可以通過安裝brew install tree使用tree命令。)

      圖2.2 我們第一個Electron應用的文件結構樹

       

      創(chuàng)建一個名為“bookmarker”的目錄,并進入此目錄。您可以通過從命令行工具運行以下兩個命令來快速創(chuàng)建這個結構。當你使用npm init之后,你會生成一個package.json文件。

      mkdir app
      touch app/main.js app/renderer.js app/style.css app/index.html

       

      Electron本身不需要這種結構,但它受到了其他Electron應用程序建立的一些最佳實踐的啟發(fā)。Atom將所有應用程序代碼保存在一個app目錄中,將所有樣式表和其他資產(chǎn)(如圖像)保存在一個靜態(tài)目錄中。LevelUI在頂層有一個index.js和一個client.js,并將所有依賴文件保存在src目錄中,樣式表保存在styles目錄中。Yoda將所有文件(包括加載應用程序其余部分的文件)保存在src目錄中。app、src和lib是存放應用程序大部分代碼的文件夾的常用名稱,style、static和assets是存放應用程序中使用的靜態(tài)資產(chǎn)的目錄的常用名稱。

       

      package.json

      package.json清單用于許多甚至說大多數(shù)Node項目。此清單包含有關項目的重要信息。它列出了元數(shù)據(jù),比如作者的姓名以及他們的電子郵件地址、項目是在哪個許可下發(fā)布的、項目的git存儲庫的位置以及文件問題的位置。它還為一些常見的任務定義了腳本,比如運行測試套件或者與我們的需求相關的構建應用程序。package.json文件還列出了用于運行和開發(fā)應用程序的所有依賴項。

      理論上,您可能有一個沒有package.json的Node項目。但是,當加載或構建應用程序時,Electron依賴于該文件及其主要屬性來確定從何處開始。

      npm是Node附帶的包管理器,它提供了一個有用的工具幫助生成package.json。在前面創(chuàng)建的“bookmarker”目錄中運行npm init。如果您將提示符留空,npm將冒號后面括號中的內(nèi)容作為默認內(nèi)容。您的內(nèi)容應該類似于圖2.3,當然,除了作者的名字之外。

      在package.json中,值得注意的是main條目。這里,你可以看到我將它設置為"./app/main.js"?;谖覀?nèi)绾卧O置應用程序。你可以指向任何你想要的文件。我們要用的主文件恰好叫做main.js。但是它可以被命名為任何東西(例如,sandwich.js、index.js、app.js)。

      圖2.3 npm init 提供一系列提示并設置一個package.json文件

       

      下載和安裝Electron在我們的項目

      我們已經(jīng)建立了應用程序的基本結構,但是卻找不到Electron。從源代碼編譯Electron需要一段時間,而且可能很乏味。因此我們根據(jù)每個平臺(macOS、Windows和Linux)以及兩種體系結構(32位和64位)預先構建了electronic版本。我們通過npm安裝Electron。

      下載和安裝電子很容易。在您運行npm init之前,在你的項目目錄中運行以下命令:

      npm install electron --save-dev

      此命令將在你的項目node_modules目錄下下載并安裝Electron(如果您還沒有目錄,它還會創(chuàng)建目錄)。--save-dev標志將其添加到package.json的依賴項列表中。這意味著如果有人下載了這個項目并運行npm install,他們將默認獲得Electron。

       

      漫談electron-prebuilt

      假如您了解Electron的歷史,您可能會看到博客文章、文檔,甚至本書的早期版本,其中提到的是electron-prebuilt,而不是electron。在過去,前者是為操作系統(tǒng)安裝預編譯版Electron的首選方法。后者是新的首選方法。從2017年初開始,不再支持electron-prebuilt。

       

      npm還允許您定義在package.json中運行公共腳本的快捷方式。當您運行package.json定義的腳本時。npm自動添加node_modules到這個路徑。這意味著它將默認使用本地安裝的Electron版本。讓我們向package.json添加一個start腳本。

      列表2.1  向package.json添加一個啟動腳本

      {                                                        +
      "name": "bookmarker",                                    |當我們運行npm start
      "version": "1.0.0",                                      |npm將會運行什么腳本
      "description": "Our very first Electron application",    |
      "main": "./app/main.js",                                 |
      "scripts": {                                             |
      "start": "electron .",                            <------+
      "test": "echo \"Error: no test specified\" && exit 1"
      },
      "author": "Steve Kinney",
      "license": "ISC",
      "dependencies": {
      "electron": "^2.0.4"
      }
      }
      ?

      現(xiàn)在,當我們運行npm start時,npm使用我們本地安裝的版本Electron去啟動Electron應用程序。你會注意到似乎沒有什么事情發(fā)生。在你的終端中,它實際運行以下程式碼:

      >bookmarker@1.0.0  start /Users/stevekinney/Projects/bookmarker
      >electron .

      您還將在dock或任務欄中看到一個新應用程序(我們剛剛設置的Electron應用程序),如圖2.4所示。它被簡稱為“Electron”,并使用Electron的默認應用程序圖標。在后面的章節(jié)中,我們將看到如何定制這些屬性,但是目前默認值已經(jīng)足夠好了。我們所有的代碼文件都是完全空白的。因此,這個應用程序還有很多操作需要去做,但是它確實存在并正確啟動。我們認為這是一場暫時的勝利。在windows上關閉應用程序的所有窗口或選擇退出應用程序菜單終止進程?;蛘?,您可以在Windows命令提示符或終端中按Control-C退出應用程序。按下Command-Period將終止macOS上的進程。

      圖2.4 dock上的應用程序就是我們剛建立的電子應用

      處理主進程

      現(xiàn)在我們有了一個Electron應用,如果我們真的能讓它做點什么,那就太好了。如果你還記得第一章,我們從可以創(chuàng)建一個或多個渲染器進程的主進程開始。我們首先通過編寫main.js代碼,邁出我們應用程序的第一步。

      要處理Electron,我們需要導入electron庫。Electron附帶了許多有用的模塊,我們在本書中使用了這些模塊。第一個—也可以說是最重要的——是app模塊。

      列表2.2 添加一個基本的主進程: ./app/main.js

      ?
      const {app} = require('electron');     +
      app.on('ready', () => {            <---+ 在應用程序完全
      console.log('Hello from Electron');   + 啟后立即調(diào)用
      });
      ?

      app是一個處理應用程序生命周期和配置的模塊。我們可以使用它退出、隱藏和顯示應用程序,以及獲取和設置應用程序的屬性。app模塊還可以運行事件-包括before-quit, window -all-closed,

      browser-window-blur, 和browser-window-focus-當應用程序進入不同狀態(tài)時。

      在應用程序完全啟動并準備就緒之前,我們無法處理它。幸運的是,app觸發(fā)了一個ready事件。這意味著在做任何事之前,我們需要耐心等待并監(jiān)聽應用程序啟動ready事件。在前面的代碼中,我們在控制臺打印日志,這是一件無需Electron就可以輕松完成的事情,但是這段代碼強調(diào)了如何偵聽ready事件。

       

      創(chuàng)建渲染器進程

      我們的主進程與其他Node進程非常相似。它可以訪問Node的所有內(nèi)置庫以及由Electron提供的一組特殊模塊,我們將在本書中對此進行探討。但是,與任何其他Node進程一樣,我們的主進程沒有DOM(文檔對象模型),也不能呈現(xiàn)UI。主進程負責與操作系統(tǒng)交互,管理狀態(tài),并與應用程序中的所有其他流程進行協(xié)調(diào)。它不負責呈現(xiàn)HTML和CSS。這就是渲染器進程的工作。參與整個Electron主要功能之一是為Node進程創(chuàng)建一個GUI。

      主進程可以使用BrowserWindow創(chuàng)建多個渲染器進程。每個BrowserWindow都是一個單獨的、惟一的渲染器器進程,包括一個DOM,訪問Chromium web APIs,以及Node內(nèi)置模塊。訪問BrowserWindow模塊的方式與訪問app模塊的方式相同。

       

      列表2.3 引用BrowserWindow模塊: ./app/main.js

      const {app, BrowserWindow} = require('electron');

       

      您可能已經(jīng)注意到BrowserWindow模塊以大寫字母開頭。根據(jù)標準JavaScript約定,這通常意味著我們用new關鍵字將其調(diào)用為構造函數(shù)。我們可以使用這個構造函數(shù)創(chuàng)建盡可能多的渲染器進程,只要我們喜歡,或者我們的計算機可以處理。當應用程序就緒時,我們創(chuàng)建一個BrowserWindow實例。讓我們按照以下方式更新代碼。

       

      列表2.4 生成一個BrowserWindow: ./app/main.js

                                                          +
      const {app, BrowserWindow} = require('electron');   |在我們的應用程序中創(chuàng)建一個
      let mainWindow = null;                         <----+window對象的全局引用
      app.on('ready', () => {                  +          +
      console.log('Hello from Electron.');    |當應用程序準備好時,
      mainWindow = new BrowserWindow();  <----+創(chuàng)建一個瀏覽器窗口
      });                                      +并將其分配給全局變量
      ?

      我們在ready事件監(jiān)聽器外聲明了mainWindow。JavaScript使用函數(shù)作用域。如果我們在事件監(jiān)聽器中聲明mainWindow, mainWindow將進行垃圾回收,因為分配給ready事件的函數(shù)已經(jīng)運行完畢。如果被垃圾回收,我們的窗戶就會神秘地消失。如果我們運行這段代碼,我們會在屏幕中央看到一個不起眼的小窗口,如圖2.5所示。

      一個沒有加載HTML文檔的空BrowserWindow

       

      這是一扇窗口,并什么好看的。下一步是將HTML頁面加載到我們創(chuàng)建的BrowserWindow實例中。所有BrowserWindow實例都有一個web content屬性,該屬性具有幾個有用的特性,比如將HTML文件加載到渲染器進程的窗口中、從主進程向渲染器進程發(fā)送消息、將頁面打印為PDF或打印機等等?,F(xiàn)在,我們最關心的是將內(nèi)容加載到我們剛剛創(chuàng)建的那個無聊的窗口中。

        我們需要加載一個HTML頁面,因此在您項目的app目錄中創(chuàng)建index.html。讓我們將以下內(nèi)容添加到HTML頁面,使其成為一個有效的文檔。

       

      列表2.5 創(chuàng)建index.html: ./app/index.html

      <!DOCTYPE html>
      <html>
      <head>
      <meta charset="UTF-8">
      <meta http-equiv="Content-Security-Policy"
      content="
      default-src 'self';
      script-src 'self' 'unsafe-inline';
      connect-src *
      "
      >
      <meta name="viewport" content="width=device-width,initial-scale=1">
      <title>Bookmarker</title>
      </head>
      <body>
      <h1>Hello from Electron</h1>
      </body>
      </html>

      這很簡單,但它完成了工作,并為構建打下了良好的基礎。我們將以下代碼添加到app/main.js中,以告訴渲染器進程在我們之前創(chuàng)建的窗口中加載這個HTML文檔。

      列表2.6 將HTML文檔加載到主窗口: ./app/main.js

      我們使用file://protocol_dirname變量,該變量在Node中全局可用。_dirname是Node進程正在執(zhí)行的目錄的完整路徑。在我的例子中,_dirname擴展為/Users/stevekinney/Projects/bookmarker/app。

      現(xiàn)在,我們可以使用npm start啟動應用程序,并觀察它加載新的HTML文件。如果一切順利,您應該會看到類似于圖2.6的內(nèi)容。

       

      從渲染進程加載代碼

      從渲染器進程加載的HTML文件中,我們可以像在傳統(tǒng)的基于瀏覽器的web應用程序中一樣加載可能需要的任何其他文件-即<script><link>標簽。

      Electron與我們習慣的瀏覽器不同之處在于我們可以訪問所有Node——甚至是我們通常認為的“客戶端”。這意味著,我們可以使用require甚至Node-only對象和變量,比如_dirnameprocess模塊。同時,我們還有所有可用的瀏覽器APIs。只能在客戶端的工作和只能在服務端做的工作的分工開始消失不見。

      圖2.6 一個帶有簡單HTML文檔的瀏覽器窗口

      讓我們來看看實際情況。在傳統(tǒng)的瀏覽器環(huán)境中_dirname不可用,在Node中documentalert是不可用的。但在Electron,我們可以無縫地將它們結合在一起。讓我們在頁面上添加一個按鈕。

      列表2.7 添加一個按鈕到HTML文檔: ./app/index. html

      <!DOCTYPE html>
      <html>
      <head>
      <meta charset="UTF+8">
      <meta http+equiv="Content+Security+Policy" content=" default+src 'self'; script+src 'self' 'unsafe+inline';connect+src *">
      <meta name="viewport" content="width=device+width,initial+scale=1">
      <title>Bookmarker</title>
      </head>
      <body>
      <h1>Hello from Electron</h1>
      <p>
      <button class="alert">Current Directory</button>     <---+
      </p>                                                     |這是我們
      </body>                                                 |的新按鈕
      </html>                                                 +
      ?

       

      現(xiàn)在,我們已經(jīng)有了按鈕,讓我們添加一個事件監(jiān)聽器,它將提醒我們運行應用程序的當前目錄。

      <script>
       const button = document.querySelector('.alert');
       button.addEventListener('click', () =^ {
       alert(__dirname);             <------+單擊按鈕時,
      });                                  |使用瀏覽器警告顯示
      </script>                             |Node全局變量
                                            +
      ?

      alert()僅在瀏覽器中可用。_dirname僅在Node中可用。當我們點擊按鈕時,我們被處理成Node和Chromium在一起工作,甜美和諧,如圖2.7所示。

      圖2.7 在渲染器進程的上下文中,BrowserWindow執(zhí)行JavaScript。

       

      在渲染器進程中引用文件

      在HTML文件中編寫代碼顯然有效,但是不難想象,我們的代碼量可能會增長到這種方法不再可行的地步。我們可以添加帶有src屬性的腳本標記來引用其他文件,但是這很快就會變得很麻煩。

      這就是web開發(fā)變得棘手的地方。雖然模塊被添加到ECMAScript規(guī)范中,目前沒有瀏覽器具有模塊系統(tǒng)的工作實現(xiàn)。在客戶端上,我們可以考慮使用一些構建工具,如Browserify (http://)或模塊bundler、webpack,也可以使用任務運行器,如GulpGrunt。

      我們可以使用Node的模塊系統(tǒng),而不需要額外的配置。讓我們移除<script>標簽中的所有代碼到-現(xiàn)在是空的-app/renderer.js文件中?,F(xiàn)在我們可以用一個<script> 標記去引用renderer.js文件去替代之前的內(nèi)容。

      列表2.9 從renderer.js加載JavaScript: ./app/index.html

                                  +
      <script>                    |使用Node的require函數(shù)
       require('./renderer'); <--+將額外的JavaScript模塊
      </script>                   |加載到渲染器進程中
                                 +

      如果我們啟動應用程序,您將看到它的功能沒有改變。一切都照常進行。這在軟件開發(fā)中很少發(fā)生。在繼續(xù)之前,讓我們先體驗一下這種感覺。

       

      在渲染器進程中添加樣式

      當我們在Electron應用程序中引用樣式表時,很少會發(fā)生意外。稍后,我們將討論如何使用Sass而不是Electron。 在電子應用程序中添加樣式表與在傳統(tǒng)web應用程序中添加樣式表沒有多大不同。盡管如此,一些細微差別還是值得討論的。

      讓我們從將style.css文件添加到應用程序目錄開始。我們將以下內(nèi)容添加到style.css中。

      列表2.10 添加基礎樣式: ./app/style.css

      html {
       box+sizing: border+box;
      }
      *, *:before, *:after {
       box+sizing: inherit;        +使用頁面所運行
      }                             |的操作系統(tǒng)的
      body, input {                 |默認系統(tǒng)字體
       font: menu;          <------+
      }
      ?

      最后一項聲明可能看起來有點陌生。它是Chromium獨有的,允許我們在CSS中使用系統(tǒng)字體。這種能力對于使我們的應用程序與其原生本機程序相適應非常重要。在macOS上,這是使用San Francisco的唯一方法,該系統(tǒng)字體附帶El Capitan 10.11及以后版本。

      在Electron應用程序中使用CSS,這是我們應該考慮的另一個重要的區(qū)別。我們的應用程序將只在應用程序附帶的Chromium版本中運行。我們不必擔心跨瀏覽器支持或兼容性考慮。正如在第1章中提到的,電子與相對較新版本的Chromium一起發(fā)布。這意味著我們可以自由地使用flexbox和CSS變量等技術。

      我們像在傳統(tǒng)瀏覽器環(huán)境中一樣引用新樣式表,然后將以下內(nèi)容添加到index.html<head>部分。 我將包含鏈接到樣式表的HTML標記—因為,在我作為web開發(fā)人員的20年里,我仍然不記得如何第一次嘗試就做到這一點。

      列表2.11 在HTML文檔中引用樣式表: ./app/index.html

      <link rel="stylesheet" href="style.css" type="text/css">

       

      實現(xiàn)用戶界面

      我們首先使用UI所需的標記更新index.html。

      列表2.12 為應用程序的UI添加標記: ./app/index.html

      <h1>Bookmarker</h1>
      <div class="error-message"></div>
      <section class="add-new-link">
       <form class="new-link-form">
         <input type="url" class="new-link-url" placeholder="URL"size="100"
          required>
         <input type="submit" class="new-link-submit" value="Submit" disabled>
       </form>
      </section>
      ?
      <section class="links"></section>
      <section class="controls">
       <button class="clear-storage">Clear Storage</button>
      </section>

       

      我們有一個用于添加新鏈接的部分,一個用于顯示所有精彩鏈接的部分,以及一個用于清除所有鏈接并重新開始的按鈕。你的應用程序中的<script>標簽應該和我們在本章早些時候討論時一樣,但是以防萬一,我在下方給出代碼:

      <script>
        require('./renderer');
      </script>

       

      標記就緒后,我們現(xiàn)在可以將注意力轉向功能。讓我們清除app/renderer.js中的所有內(nèi)容,重新開始。在我們一起學習的過程中,我們將需要處理添加到標記中的一些元素,所以讓我們首先查詢這些選擇器并將它們緩存到變量中。將以下內(nèi)容添加到app/renderer.js。

      列表2.13 緩存DOM元素選擇器: ./app/renderer.js

      const  linksSection = document.querySelector('.links');
      const errorMessage = document.querySelector('.error-message');
      const newLinkForm = document.querySelector('.new-link-form');
      const newLinkUrl = document.querySelector('.new-link-url');
      const newLinkSubmit = document.querySelector('.new-link-submit');
      const clearStorageButton = document.querySelector('.clear-storage');

       

      回顧清單2.12,您會注意到在標記中我們將input元素的type屬性設置“url”。如果內(nèi)容不匹配有效的URL模式,Chromium將把該字段標記為無效。不幸的是,我們無法訪問Chrome或Firefox中內(nèi)置的錯誤消息彈出框。這些彈出窗口不是Chromium web模塊的一部分,因此也不是Electron的一部分?,F(xiàn)在,我們在默認情況下禁用start按鈕,然后在每次用戶在URL輸入框內(nèi)中鍵入字母時檢查是否有一個有效的URL語法。

      如果用戶提供了一個有效的URL,那么我們將打開submit按鈕并允許他們提交URL。讓我們將這段代碼添加到app/renderer.js中。

       

      列表2.14 添加事件監(jiān)聽器以啟用submit按鈕

      newLinkUrl.addEventListener('keyup', () => {
       newLinkSubmit.disabled = !newLinkUrl.validity.valid;    <------+
      });                        當用戶在輸入字段中敲入url時               |
                                通過使用Chromium ValidityState API     |
                                來確定輸入是不是有效,如果是這樣,從         +
                      submit按鈕中移除disable屬性

       

      現(xiàn)在也是添加一個協(xié)助函數(shù)來清除URL字段內(nèi)容的好時機。在理想的情況下,只要成功存儲了鏈接,就會調(diào)用這個函數(shù)。

      列表2.15 添加幫助函數(shù)來清除輸入框: ./app/renderer.js

                                         +
      const clearForm= () => {           |通過設置新連接輸入框為空
       newLinkUrl.value = null;    <----+來清除該字段
      };                                 |
                                        +
      ?

       

      當用戶提交一個鏈接,我們希望瀏覽器請求URL,然后把獲取回復體,解析它,找到title元素,得到標題的文本元素,存儲書簽的標題和URL在localStorage,和then-finally-update書簽的頁面。

       

      在Electron實現(xiàn)跨域請求

      你可能感覺到,也可能沒有感覺到,你脖子后面的一些毛發(fā)開始豎起來。你甚至可能對自己說:“這個計劃不可能行得通。您不能向第三方服務器發(fā)出請求。瀏覽器不允許這樣做。”

      通常來說,你是對的。在傳統(tǒng)的基于瀏覽器的應用程序中,不允許客戶端代碼向其他服務器發(fā)出請求。通常,客戶端代碼向服務器發(fā)出請求,然后將請求代理給第三方服務器。當它返回時,它將響應代理回客戶機。我們在第一章中討論了這背后的一些原因。

      Electron具有Node服務器的所有功能,以及瀏覽器的所有功能。這意味著我們可以自由地發(fā)出跨源請求,而不需要服務器。

      在Electron中編寫應用程序的另一個好處是我們可以使用正在興起的Fetch API來向遠程服務器發(fā)出請求。Fetch API免去了手工設置XMLHttpRequest的麻煩,并為處理我們的請求提供了一個良好的、基于承諾的接口。在撰寫本文時,主要瀏覽器對Fetch的支持有限。也就是說,它在當前版本的Chromium中有完整的支持,這意味著我們可以使用它。

      我們向表單添加一個事件偵聽器,以便在表單有動作時,立即執(zhí)行提交。我們沒有服務器,所以需要確保避免發(fā)出請求的默認操作。我們通過防止默認操作來做到這一點。我們還緩存URL輸入字段的值,以便將來使用。

       

      列表2.16 向submit按鈕添加事件偵聽器: ./app/renderer.js

      newLinkForm.addEventListener('submit', (event) => {
       event.preventDefault();              <-----+告訴Chromium不要觸發(fā)HTTP請求,
                                                  |這是表單提交的默認操作
       const url = newLinkUrl.value;  <--+        |
                                         |        +
      // More code to come...             |獲取新鏈接輸入框中的URL字段,
      });                                 +我們很塊就會用到這個值。
      ?

       

      Fetch API作為全局可用的fetch變量。抓取的URL返回一個promise對象,該對象將在瀏覽器完成時被實現(xiàn) 獲取遠程資源。使用這個promise對象,我們可以根據(jù)是否獲取網(wǎng)頁、圖像或其他類型的內(nèi)容來處理不同的響應。在本例中,我們正在獲取一個網(wǎng)頁,因此我們將響應轉換為文本。我們從事件監(jiān)聽器中的以下代碼開始。

      列表2.17 使用Fetch API請求遠程資源./app/renderer.js

      fetch(url)  //使用Fetch API獲取提供的URL的內(nèi)容
      .then(response => response.text()); //將響應解析為純文本

       

      Promises是鏈式的,我們可以使用先前承諾的返回值,并將另一個調(diào)用附加到then。此外,response.text()本身返回一個promise。我們的下一步將是獲取接收到的大塊標記,并解析它來遍歷它并找到title元素。

      解析回復報文

      Chromium提供了一個解析器,它將為我們做這件事,但是我們需要實例化它。在app/renderer的頂部。我們創(chuàng)建了一個DOMParser實例,并將其存儲起來供以后使用。

      列表2.18 實例化一個DOMParser: ./app/renderer.js

      const parser = new DOMParser(); //創(chuàng)建一個DOMParser實例。我們將在獲取所提供URL的文本內(nèi)容后使用此方法。

       

      讓我們設置一對幫助函數(shù)來解析響應并為我們找到標題。

      列表2.19 添加用于解析響應和查找標題的函數(shù): ./app/renderer.js

      const parseResponse = (text) => {
      return parser.parseFromString(text, 'text/html'); //從URL獲取HTML字符串并將其解析為DOM樹。
      }
      const findTitle = (nodes) =>{
      return nodes.querySelector('title').innerText; //遍歷DOM樹以找到標題節(jié)點。
      }

       

      現(xiàn)在我們可以將這兩個步驟添加到我們的處理鏈中。

      列表2.20 解析響應并在獲取頁面時查找標題: ./app/renderer.js

      fetch(url)
      .then(response => response.text())
      .then(parseResponse)
      .then(findTitle);

       

      此時,app/renderer.js中的代碼看起來是這樣的。

      const parser = new DOMParser();
      const linksSection = document.querySelector('.links');
      const errorMessage = document.querySelector('.error-message');
      const newLinkForm = document.querySelector('.new-link-form');
      const newLinkUrl = document.querySelector('.new-link-url');
      const newLinkSubmit = document.querySelector('.new-link-submit');
      const clearStorageButton = document.querySelector('.clear-storage');
      ?
      newLinkUrl.addEventListener('keyup', () => {
      newLinkSubmit.disabled = !newLinkUrl.validity.valid;
      });
      ?
      newLinkForm.addEventListener('submit', (event) => {
      event.preventDefault();
      const url = newLinkUrl.value;
      fetch(url)
      .then(response => response.text())
      .then(parseResponse)
      .then(findTitle)
      });
      ?
      const clearForm = () => {
      newLinkUrl.value = null;
      }
      ?
      const parseResponse = (text) => {
      return parser.parseFromString(text, 'text/html');
      }
      ?
      const findTitle = (nodes) => {
      return nodes.querySelector('title').innerText;
      }

       

      使用web storage APIs存儲響應

      localStorage是一個簡單的鍵/值存儲,內(nèi)置在瀏覽器中并持久保存之間的會話。您可以在任意鍵下存儲簡單的數(shù)據(jù)類型,如字符串和數(shù)字。讓我們設置另一個幫助函數(shù),它將從標題和URL生成一個簡單的對象,使用內(nèi)置的JSON庫將其轉換為字符串,然后使用URL作為鍵存儲它。

      圖2.22 創(chuàng)建一個函數(shù)來在本地存儲中保存鏈接: ./app/renderer.js

      const storeLink = (title, url) => {
      localStorage.setItem(url, JSON.stringify({ title: title, url: url }));
      };

       

      我們的新storeLink函數(shù)需要標題和URL來完成它的工作,但是前面的處理只返回標題。我們使用一個箭頭函數(shù)將對storeLink的調(diào)用封裝在一個匿名函數(shù)中,該匿名函數(shù)可以訪問作用域中的url變量。如果成功,我們也清除表單。

      圖2.23 存儲鏈接并在獲取遠程資源時清除表單: ./app/renderer.js

                                                  
      fetch(url)                                  
      .then(response => response.text())        
      .then(parseResponse)                       |
      .then(findTitle)                           |將標題和URL存儲到localStorage
      .then(title => storeLink(title, url))  <---+
      .then(clearForm);
      ?

       

      顯示請求結果

      存儲鏈接是不夠的。我們還希望將它們顯示給用戶。這意味著我們需要創(chuàng)建功能來遍歷存儲的所有鏈接,將它們轉換為DOM節(jié)點,然后將它們添加到頁面中。

      讓我們從從localStorage獲取所有鏈接的能力開始。如果你還記得,localStorage是一個鍵/值存儲。我們可以使用對象。獲取對象的所有鍵。我們必須為自己提供另一個幫助函數(shù)來將所有鏈接從localStorage中取出。這沒什么大不了的,因為我們需要將它們從字符串轉換回實際對象。讓我們定義一個getLinks函數(shù)。

      圖2.24 創(chuàng)建用于從本地存儲中獲取鏈接的函數(shù): ./app/renderer.js

                                             
                                           
      const getLinks = () => {               |
                                            |獲取當前存儲在localStorage中的所有鍵的數(shù)組
       return Object.keys(localStorage) <---+
        .map(key => JSON.parse(localStorage.getItem(key)));   <----+
      }                                                              |對于每個鍵,獲取其值
                                                                    |并將其從JSON解析為JavaScript對象
                                                                   
      ?

       

      接下來,我們將這些簡單的對象轉換成標記,以便稍后將它們添加到DOM中。我們創(chuàng)建了一個簡單的convertToElement 幫助函數(shù),它也可以處理這個問題。需要指出的是,我們的convertToElement函數(shù)有點幼稚,并且不嘗試清除用戶輸入。理論上,您的應用程序很容易受到腳本注入攻擊。這有點超出了本章的范圍,所以我們只做了最低限度的渲染這些鏈接到頁面上。我將把它作為練習留給讀者來確保這個特性的安全性。

      列表2.25 創(chuàng)建一個從鏈接數(shù)據(jù)創(chuàng)建DOM節(jié)點的函數(shù): ./app/renderer.js

      const convertToElement = (link) => {
      return `
      <div class="link">
      <h3>${link.title}</h3>
      <p>
      <a href="${link.url}">${link.url}</a>
      </p>
      </div>
      `;
      };

       

      最后,我們創(chuàng)建一個renderLinks()函數(shù),它調(diào)用getLinks,連接它們,使用convertToElement()轉換集合,然后替換頁面上的linksSection元素。

      列表2.26 創(chuàng)建一個函數(shù)來呈現(xiàn)所有鏈接并將它們添加到DOM中: ./app/renderer.js

      const renderLinks = () => {
      const linkElements = getLinks().map(convertToElement).join(''); //將所有鏈接轉換為HTML元素并組合它們
      linksSection.innerHTML = linkElements; //用組合的鏈接元素替換links部分的內(nèi)容
      };

       

      現(xiàn)在我們可以往處理鏈上添加最后一步。

      列表2.27 獲取遠程資源后呈現(xiàn)鏈接: ./app/renderer.js

      fetch(url)
      .then(response => response.text())
      .then(parseResponse)
      .then(findTitle)
      .then(title => storeLink(title, url))
      .then(clearForm)
      .then(renderLinks);

       

      當頁面初始加載時,我們還通過在頂層范圍內(nèi)調(diào)用renderLinks()來呈現(xiàn)所有鏈接。

      列表2.28 加載和渲染鏈接: ./app/renderer.js

      renderLinks();  //一旦頁面加載,就調(diào)用我們之前創(chuàng)建的renderLinks()函數(shù)

       

      使用promise與將功能分解為命名的幫助函數(shù)相協(xié)調(diào)的一個優(yōu)點是,我們的代碼通過獲取外部頁面、解析它、存儲結果和重新對鏈接列表進行排序的過程非常清楚。

      最后一件事,我們需要完成我們的簡單應用程序的所有功能安裝的方法是連接“清除存儲”按鈕。我們在localStorage上調(diào)用clear方法,然后在linksSection中清空列表。

      列表2.29 編寫清除存儲按鈕: ./app/renderer.js

      clearStorageButton.addEventListener('click', () => {
      localStorage.clear(); //清空localStorage中的所有鏈接
      linksSection.innerHTML = '';    //從UI上移除所有鏈接
      });

       

      有了Clear Storage按鈕,似乎我們已經(jīng)具備了大部分功能。我們的應用程序現(xiàn)在看起來如圖2.8所示。此時,呈現(xiàn)器過程的代碼應該如清單2.30所示。

      列表2.30 獲取、存儲和呈現(xiàn)鏈接的渲染器進程: ./app/renderer.js

      const parser = new DOMParser();
      const linksSection = document.querySelector('.links');
      const errorMessage = document.querySelector('.error-message');
      const newLinkForm = document.querySelector('.new-link-form');
      const newLinkUrl = document.querySelector('.new-link-url');
      const newLinkSubmit = document.querySelector('.new-link-submit');
      const clearStorageButton = document.querySelector('.clear-storage');
      const newLinkUrl.addEventListener('keyup', () => {
      const newLinkSubmit.disabled = !newLinkUrl.validity.valid;
      });
      newLinkForm.addEventListener('submit', (event) => {
      event.preventDefault();
         
      const url = newLinkUrl.value;
         
      fetch(url)
      .then(response => response.text())
      .then(parseResponse)
      .then(findTitle)
      .then(title => storeLink(title, url))
      .then(clearForm)
      .then(renderLinks);
      });
      ?
      clearStorageButton.addEventListener('click', () => {
      localStorage.clear();
      linksSection.innerHTML = '';
      });
      ?
      const clearForm = () => {
      newLinkUrl.value = null;
      }
      ?
      const parseResponse = (text) => {
      return parser.parseFromString(text, 'text/html');
      }
      ?
      const findTitle = (nodes) => {
      return nodes.querySelector('title').innerText;
      }
      ?
      const storeLink = (title, url) => {
      localStorage.setItem(url, JSON.stringify({ title: title, url: url }));
      }
      ?
      const getLinks = () => {
      return Object.keys(localStorage)
      .map(key => JSON.parse(localStorage.getItem(key)));
      }
      ?
      const convertToElement = (link) => {
      return `<div class="link"><h3>${link.title}</h3>
      <p><a href="${link.url}">${link.url}</a></p></div>`;
      }
      ?
      const renderLinks = () => {
      const linkElements = getLinks().map(convertToElement).join('');
      linksSection.innerHTML = linkElements;
      }
      ?
      renderLinks();

       

      錯誤的請求路徑

      到目前為止,一切似乎都運轉良好。我們的應用程序從外部頁面獲取標題,在本地存儲鏈接,在頁面上呈現(xiàn)鏈接,并在需要時從頁面中清除它們。

      但是如果出了什么問題呢?如果我們給它一個無效鏈接會發(fā)生什么?如果請求超時會發(fā)生什么?我們將處理兩種最可能的情況:當用戶提供一個URL,該URL通過了輸入字段的驗證檢查,但實際上并不有效;當URL有效,但服務器返回400或500級錯誤時。

      我們添加的第一件事是處理任何錯誤的能力。我們需要提供一個捕獲異常的方法,當出現(xiàn)錯誤的時候,進行調(diào)用。我們在這個事件中定義了另一個幫助方法。

      圖2.31 顯示錯誤消息: ./app/renderer.js

      const handleError = (error, url) => {                     +如果獲取鏈接失敗,
        errorMessage.innerHTML = `                             |則設置錯誤消息元素的內(nèi)容
        There was an issue adding "${url}": ${error.message}   |         +
        `.trim();                                         <----+         |
        setTimeout(() => errorMessage.innerText = null, 5000);      <----+5秒后清除錯誤消息
      }                                                                   +
      ?

       

      我們可以把它加到鏈上。我們使用另一個匿名函數(shù)傳遞帶有錯誤消息的URL。這主要是為了提供更好的錯誤消息。如果不希望在錯誤消息中包含URL,則沒有必要這樣做。

      圖2.32 在獲取、解析和呈現(xiàn)鏈接時捕獲錯誤: ./app/renderer.js

      fetch(url)
       .then(response => response.text())
       .then(parseResponse)                          +
       .then(findTitle)                              |
       .then(title => storeLink(title, url))         |如果此處理鏈中的任何錯誤拒絕或拋出錯誤
       .then(clearForm)                              |則捕獲錯誤并將其顯示在UI中
       .then(renderLinks)                            |
       .catch(error => handleError(error, url));  <--+
      ?

       

      我們還在前面添加了一個步驟,用于檢查請求是否成功。如果是,它將請求傳遞給處理鏈中的下一個操作。如果沒有成功,那么我們將拋出一個錯誤,這將繞過處理鏈中的其余操作,并直接跳到handleError()步驟。這里有一個我沒有處理的異常情況:如果Fetch API不能建立網(wǎng)絡連接,那么它返回的承諾將被完全拒絕。我把它作為練習留給讀者來處理,因為我們在這本書中有很多內(nèi)容要講,而且頁數(shù)有限。響應。如果狀態(tài)碼在400或500范圍內(nèi),response.ok將為false。

      圖2.33 驗證來自遠程服務器的響應: ./app/renderer.js

                                                          +
                                                         |如果響應成功,則將其
      const validateResponse = (response) => {            |傳遞給下一個處理鏈
       if (response.ok) { return response; }        <-----+
       throw new Error(`Status code of ${response.status} +
        ${response.statusText}`);           <-----+
      }                                            |如果請求收到400或500系列響應
                                                 +則引發(fā)錯誤。
      ?

       

      如果沒有錯誤,此代碼將傳遞響應對象。但是,如果出現(xiàn)錯誤,它會拋出一個錯誤,handleError()會捕捉到這個錯誤并相應地進行處理。

      圖2.34 在處理鏈中添加validateResponse(): ./app/renderer.js

      fetch(url)
      .then(validateResponse)
      .then(response => response.text())
      .then(parseResponse)
      .then(findTitle)
        .then(title => storeLink(title, url))
      .then(clearForm)
      .then(renderLinks)
      .catch(error => handleError(error, url));

       

      一個意想不到的錯誤

      我們還沒有走出困境——如果一切順利的話,我們還有一個問題。如果單擊應用程序中的一個鏈接會發(fā)生什么?也許并不奇怪,它指向了那個鏈接。我們的Electron應用程序的Chromium部分認為它是一個web瀏覽器,所以它做了web瀏覽器最擅長的事情—它進入頁面。

      只是我們的應用程序并不是真正的web瀏覽器。它缺少后退按鈕或位置欄等重要功能。如果我們點擊應用程序中的任何鏈接,我們就會幾乎被困在那里。我們唯一的選擇是關閉應用程序,重新開始。

      解決方案是在真正的瀏覽器中打開鏈接。但這引出了一個問題,哪個瀏覽器?我們?nèi)绾沃烙脩魧⑹裁丛O置為默認瀏覽器?我們當然不想做任何僥幸的猜測,因為我們不知道用戶安裝了什么瀏覽器,而且沒有人喜歡看到錯誤的應用程序僅僅因為他們點擊了一個鏈接就開始打開。 ? Electron隨shell模塊一起載運,shell模塊提供了一些與之相關的功能,高級桌面集成。shell模塊可以詢問用戶的操作系統(tǒng)他們更喜歡哪個瀏覽器,并將URL傳遞給要打開的瀏覽器。讓我們從引入Electron開始,并在app/renderer.js的頂部存儲對其shell模塊的引用。

      列表2.35 引用Electron的shell 模塊: ./app/renderer.js

      const {shell} = require('electron');

       

      我們可以使用JavaScript來確定我們希望在應用程序中處理哪些url,以及我們希望將哪些url傳遞給默認瀏覽器。在我們的簡單應用程序中,區(qū)別很簡單。我們希望所有的鏈接都在默認瀏覽器中打開。這個應用程序中正在添加和刪除鏈接,因此我們在linksSection元素上設置了一個事件監(jiān)聽器,并允許單擊事件彈出。如果目標元素具有href屬性,我們將阻止默認操作并將URL傳遞給默認瀏覽器。

      列表2.36 在默認瀏覽器中打開鏈接: ./app/renderer.js

                                                           +
                                                          |通過查找href屬性
                                                          |檢查被單擊的元素是否為鏈接
      linksSection.addEventListener('click', (event) => {  |
       if (event.target.href) {                       <---+
         event.preventDefault();                    <----+
         shell.openExternal(event.target.href); <--+     |如果它不是一個連接,
      }                                           |     |不打開
      Uses Electron’s shell module                  |     +
      });                 在默認瀏覽器中使用Electorn   |
                          打開鏈接                   +
      ?

       

      通過相對簡單的更改,我們的代碼的行為就像預期的那樣。單擊鏈接將在用戶的默認瀏覽器中打開該頁。我們有一個簡單但功能齊全的桌面應用程序了。

      我們完成的代碼應該如下面的代碼示例所示。你可能以不同的順序使用您的功能。

      列表2.37 完成的應用程序: ./app/renderer.js

      const {shell} = require('electron');
      ?
      const parser = new DOMParser();
      ?
      const linksSection = document.querySelector('.links');
      const errorMessage = document.querySelector('.error-message');
      const newLinkForm = document.querySelector('.new-link-form');
      const newLinkUrl = document.querySelector('.new-link-url');
      const newLinkSubmit = document.querySelector('.new-link-submit');
      const clearStorageButton = document.querySelector('.clear-storage');
      ?
      newLinkUrl.addEventListener('keyup', () => {
       newLinkSubmit.disabled = !newLinkUrl.validity.valid;
      });
      ?
      newLinkForm.addEventListener('submit', (event) => {
       event.preventDefault();
      ?
       const url = newLinkUrl.value;
      ?
       fetch(url)
        .then(response => response.text())
        .then(parseResponse)
        .then(findTitle)
        .then(title => storeLink(title, url))
        .then(clearForm)
        .then(renderLinks)
        .catch(error => handleError(error, url));
      });
      ?
      clearStorageButton.addEventListener('click', () => {
       localStorage.clear();
       linksSection.innerHTML = '';
      });
      ?
      linksSection.addEventListener('click', (event) => {
       if (event.target.href) {
         event.preventDefault();
         shell.openExternal(event.target.href);
      }
      });
      ?
      ?
      const clearForm = () => {
       newLinkUrl.value = null;
      }
      ?
      const parseResponse = (text) => {
       return parser.parseFromString(text, 'text/html');
      }
      ?
      const findTitle = (nodes) => {
       return nodes.querySelector('title').innerText;
      }
      ?
      const storeLink = (title, url) => {
       localStorage.setItem(url, JSON.stringify({ title: title, url: url }));
      }
      ?
      const getLinks = () => {
       return Object.keys(localStorage)
                    .map(key => JSON.parse(localStorage.getItem(key)));
      }
      ?
      const convertToElement = (link) => {
       return `<div class="link"><h3>${link.title}</h3>
               <p><a href="${link.url}">${link.url}</a></p></div>`;
      }
      ?
      const renderLinks = () => {
       const linkElements = getLinks().map(convertToElement).join('');
       linksSection.innerHTML = linkElements;
      }
      ?
      const handleError = (error, url) => {
       errorMessage.innerHTML = `
         There was an issue adding "${url}": ${error.message}
       `.trim();
       setTimeout(() => errorMessage.innerText = null, 5000);
      }
      ?
      const validateResponse = (response) => {
       if (response.ok) { return response; }
       throw new Error(`Status code of ${response.status} ${response.statusText}`);
      }
      ?
      renderLinks();

       

      總結

      • Electron不推薦或強制執(zhí)行特定的項目結構。

      • Electron使用npm的package.json清單來決定那個文件被加載作為主進程

      • 我們通過使用npm init從樣板文件中生產(chǎn)package.json

      • 我們通常在每個項目中都在本地安裝Electron。這允許我們有特定項目版本的Electron。

      • 我們可以在Electron應用程序中使用require('electron')來訪問Electron特定的模塊和功能。

      • app模塊管理電子應用的生命周期。

      • 主進程無法呈現(xiàn)UI。

      • 我們可以使用Browser-window模塊從主進程創(chuàng)建渲染進程

      • Electron允許我們直接從第三方服務器發(fā)出請求,并不需要中間服務器的代理。傳統(tǒng)的web應用程序則不允許這樣做。

      • 在localStorage中存儲數(shù)據(jù)將允許它在我們退出并重新打開時保持。

       

      我的博客即將同步至騰訊云+社區(qū),邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=2zcuec310v8kg

      原文出處:https://www.cnblogs.com/sanshengshui/p/11066103.html

        本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內(nèi)容,請點擊一鍵舉報。
        轉藏 分享 獻花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多