從零搭建一個Electron應用

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Electron 是一個優秀的跨平臺桌面應用程序開源庫,目前接觸 Electron 的開發者也越來越多。但是筆者發現,目前社區裏缺少對初學者足夠友好的入門教程來幫助初學者用 Electron 搭建一個完整的開發框架。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了解決這個問題,筆者將結合平時的一些 Electron 開發經驗,漸近式的帶領讀者從零開始搭建一個完整的 Electron 應用。在這個教程中,筆者將使用 React 構建渲染進程。當然,讀者也可以用其他框架來構建渲染進程,各種前端框架腳手架已經足夠友好,所以這一點不用擔心。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"閱讀完這篇教程,讀者將會了解到:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" - Electron的核心知識點"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" - 如何搭建一個最簡單的 Electron"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" - 如何將 Electron 和前端應用相結合"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" - 如何配置 TypeScript 以保證代碼質量"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" - 如何跨平臺打包 Electron 應用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" - 如何調試 Electron"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"筆者將通過以下 8 個小 Demo 來介紹上面的知識點,爲了保證學習質量,建議讀者手把手跟着練習這些 Demo,讀者可以"},{"type":"link","attrs":{"href":"https://github.com/WangYuLue/electron-demos","title":""},"content":[{"type":"text","text":"點擊這裏"}]},{"type":"text","text":"來下載項目代碼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"搭建一個最簡單的Electron"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從零搭建一個React應用(TypeScript,Scss,熱更新)"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"將 Electron 與 React 結合"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"打包 Electron 應用"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"實際開發一個小 Demo"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"主進程使用 TypeScript 構建"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"主進程監聽文件變化並重啓"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 vscode 中調試主進程和渲染進程"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在開始之前,我們先聊一聊 Electron 的基礎概念"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Electron 基礎概念"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Electron 是什麼?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Electron 是一個可以用 JavaScript、HTML 和 CSS 構建桌面應用程序的庫。這些應用程序能打包到 Mac、Windows 和 Linux 系統上運行,也能上架到 Mac 和 Windows 的 App Store。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Electron 由什麼組成?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Electron 結合了 Chromium、Node.js 以及 操作系統本地的 API(如打開文件窗口、通知、圖標等)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"一些歷史"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2013年4月Atom Shell 項目啓動 。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2014年5月Atom Shell 被開源 。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2015年4月Atom Shell 被重命名爲 Electron 。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2016年5月Electron 發佈了 v1.0.0 版本 。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2016年5月Electron 構建的應用程序可上架 Mac App Store 。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2016年8月Windows Store 支持 Electron 構建的應用程序 。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Electron 基礎架構"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Electron 與 Chromium 在架構上很相似"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Chromium運行時有一個 "},{"type":"codeinline","content":[{"type":"text","text":"Browser Process"}]},{"type":"text","text":",以及一個或者多個 "},{"type":"codeinline","content":[{"type":"text","text":"Renderer Process"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Renderer Process"}]},{"type":"text","text":" 顧名思義負責渲染Web頁面。"},{"type":"codeinline","content":[{"type":"text","text":"Browser Process"}]},{"type":"text","text":" 則負責管理各個 "},{"type":"codeinline","content":[{"type":"text","text":"Renderer Process"}]},{"type":"text","text":" 以及其他部分(比如菜單欄,收藏夾等等),如下圖:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/07/0713e4dac97ca1c937f5e9c86624ab6e.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 Electron中,結構仍然類似,不過這裏是一個 "},{"type":"codeinline","content":[{"type":"text","text":"Main Process"}]},{"type":"text","text":" 管理多個 "},{"type":"codeinline","content":[{"type":"text","text":"Renderer Process"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b2/b2fb3ed4de65b4410e6761074771dc86.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而且在 "},{"type":"codeinline","content":[{"type":"text","text":"Renderer Process"}]},{"type":"text","text":" 可以使用 "},{"type":"codeinline","content":[{"type":"text","text":"Node.js"}]},{"type":"text","text":" 的 API,這就賦予來 Electron 極大的能力,以下是主進程以及渲染進程可以訪問到的API:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b5/b5176047251cef39b74152227e02c8b3.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"如何將 Chromium 與 Node 整合"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Electron 最讓人興奮的地方在於 Chromium 與 Node 的整合。通俗的講,我們可以在 Chromium 的控制檯上做任何 Node 可以做的事。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"能夠做這個整合,首先得益於 Chromium 和 Node.js 都是基於 v8 引擎來執行 js 的,所以給了一種可能,他們是可以一起工作的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是有一個問題,Chromium 和 Node.js 的事件循環機制不同。我們知道,Node.js 是基於 libuv 的,Chromium 也有一套自己的事件循環方式,要讓他們一起工作,就必須整合這兩個事件循環機制。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e4/e4776763c3992d3723a3d1c50775941e.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如上圖所示,Electron 採用了這樣一種方式,它起了一個新的線程輪詢 libuv 中的 backend fd,從而監聽 Node.js 中的事件,一旦發現有新的事件發生,就會立即把它 post 到 Chromium 的事件循環中,喚醒主線程處理這個事件。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Electron 與 NW.js 的對比以及區別"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"和 Electron 同樣出名的跨平臺桌面應用開源庫還有 NW.js。他們都有非常出名的應用,例如用Electron開發的有 vscode,用 NW.js 開發的有釘釘。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Electron"}]},{"type":"text","text":" 的原名叫 "},{"type":"codeinline","content":[{"type":"text","text":"Atom Shell"}]},{"type":"text","text":","},{"type":"codeinline","content":[{"type":"text","text":"NW.js"}]},{"type":"text","text":" 的原名叫 "},{"type":"codeinline","content":[{"type":"text","text":"node-webkit"}]},{"type":"text","text":";他們起初是同一個作者開發,而且這個這個作者是國人,先向"},{"type":"link","attrs":{"href":"https://github.com/zcbenz","title":""},"content":[{"type":"text","text":"大佬"}]},{"type":"text","text":"致敬,爲我們開源這麼優秀的開源工具。後來種種原因分爲兩個產品,一個命名爲 "},{"type":"codeinline","content":[{"type":"text","text":"NW.js"}]},{"type":"text","text":"(英特爾公司提供技術支持)、 另一命名爲 "},{"type":"codeinline","content":[{"type":"text","text":"Electron"}]},{"type":"text","text":"(Github 公司提供技術支持)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"兩者在GitHub上的數據對比"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"nw.js (36.7k star, 4051 commits, 256 releases, 748 open issues, 5862 closed)\nelectron (81.7k star, 23364 commits, 849 releases, 1047 open issues, 11612 closed)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看出 "},{"type":"codeinline","content":[{"type":"text","text":"Electron"}]},{"type":"text","text":" 更加活躍。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"兩者程序的入口不同"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 "},{"type":"codeinline","content":[{"type":"text","text":"NW.js"}]},{"type":"text","text":" 中,應用的主入口是網頁或者JS腳本。 你需要在 "},{"type":"codeinline","content":[{"type":"text","text":"package.json"}]},{"type":"text","text":" 中指定一個html或者js文件,一旦應用的主窗口(在html作爲主入口點的情況下)或腳本被執行,應用就會在瀏覽器窗口打開。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 "},{"type":"codeinline","content":[{"type":"text","text":"Electron"}]},{"type":"text","text":" 中,入口是一個 "},{"type":"codeinline","content":[{"type":"text","text":"JavaScript"}]},{"type":"text","text":" 腳本。 不同於直接提供一個URL,"},{"type":"text","marks":[{"type":"strong"}],"text":"你需要手動創建一個瀏覽器窗口"},{"type":"text","text":",然後通過 API 加載 HTML 文件。 你還可以監聽窗口事件,決定何時讓應用退出。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Electron 的工作方式更像 Node.js 運行時 ,Electron 的 APIs 更加底層。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Node 集成"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 "},{"type":"codeinline","content":[{"type":"text","text":"NW.js"}]},{"type":"text","text":",網頁中的 Node 集成需要通過給 "},{"type":"codeinline","content":[{"type":"text","text":"Chromium"}]},{"type":"text","text":" 打補丁來實現。但在 "},{"type":"codeinline","content":[{"type":"text","text":"Electron"}]},{"type":"text","text":" 中,我們選擇了另一種方式:通過各個平臺的消息循環與 "},{"type":"codeinline","content":[{"type":"text","text":"libuv"}]},{"type":"text","text":" 的循環集成,避免了直接在 "},{"type":"codeinline","content":[{"type":"text","text":"Chromium"}]},{"type":"text","text":" 上做改動。這就意味着 "},{"type":"codeinline","content":[{"type":"text","text":"Electron"}]},{"type":"text","text":" 迭代的成本更低。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"準備工作"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有了上面這些基礎概念,接下來開始將下面 8 個 Demo 的學習。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在開始之前,我們先做一些基礎的準備,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"安裝依賴:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"bash"},"content":[{"type":"text","text":"yarn\n\n# or\n\nnpm install"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了防止意外報錯,我們約定 "},{"type":"codeinline","content":[{"type":"text","text":"cd"}]},{"type":"text","text":" 到每個 demo 裏來運行相應 "},{"type":"codeinline","content":[{"type":"text","text":"package.json"}]},{"type":"text","text":" 中的腳本。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以下的 demo 都將基於 Electron 8.0.0 版本講解。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Demo01: 搭建一個最簡單的 Electron"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,我們會搭建一個最簡單的 Electron 應用,它只有 3 個文件,"},{"type":"link","attrs":{"href":"https://github.com/WangYuLue/electron-demos/tree/master/demo01","title":""},"content":[{"type":"text","text":"點這裏"}]},{"type":"text","text":"查看Demo01代碼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"創建 "},{"type":"codeinline","content":[{"type":"text","text":"demo01"}]},{"type":"text","text":" 目錄:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、新建 "},{"type":"codeinline","content":[{"type":"text","text":"package.json"}]},{"type":"text","text":" 文件"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"json"},"content":[{"type":"text","text":"{\n \"name\": \"demo01\",\n \"version\": \"1.0.0\",\n \"main\": \"main.js\",\n \"scripts\": {\n \"start\": \"../node_modules/.bin/electron .\"\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、新建 "},{"type":"codeinline","content":[{"type":"text","text":"index.html"}]},{"type":"text","text":" 文件"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"html"},"content":[{"type":"text","text":"\n\n \n \n Hello World!\n \n \n

Hello World!

\n We are using node ,\n Chrome ,\n and Electron .\n \n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、新建 "},{"type":"codeinline","content":[{"type":"text","text":"main.js"}]},{"type":"text","text":" 文件"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"js"},"content":[{"type":"text","text":"const { app, BrowserWindow } = require('electron')\n\nfunction createWindow () { \n // 創建瀏覽器窗口\n let win = new BrowserWindow({\n width: 800,\n height: 600,\n webPreferences: {\n nodeIntegration: true\n }\n })\n\n // 加載index.html文件\n win.loadFile('index.html')\n}\n\napp.whenReady().then(createWindow)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"運行 "},{"type":"codeinline","content":[{"type":"text","text":"yarn start"}]},{"type":"text","text":",第一個 electron 項目就輕鬆啓動起來了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"注意 "},{"type":"codeinline","content":[{"type":"text","text":"package.json"}]},{"type":"text","text":" 中的 "},{"type":"codeinline","content":[{"type":"text","text":"main"}]},{"type":"text","text":" 字段,它指定了 electron 的入口文件。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 "},{"type":"codeinline","content":[{"type":"text","text":"main.js"}]},{"type":"text","text":" 中,我們注意到,"},{"type":"codeinline","content":[{"type":"text","text":"electron"}]},{"type":"text","text":" 模塊所提供的功能都是通過命名空間暴露出來的。 比如說: "},{"type":"codeinline","content":[{"type":"text","text":"electron.app"}]},{"type":"text","text":" 負責管理 "},{"type":"codeinline","content":[{"type":"text","text":"Electron"}]},{"type":"text","text":" 應用程序的生命週期, "},{"type":"codeinline","content":[{"type":"text","text":"electron.BrowserWindow"}]},{"type":"text","text":" 類負責創建窗口。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"細心的同學注意到,Demo01 其實是 Electorn 官方文檔中的"},{"type":"link","attrs":{"href":"https://www.electronjs.org/docs/tutorial/first-app","title":""},"content":[{"type":"text","text":"範例"}]},{"type":"text","text":";是的,官方的範例寫的非常簡單友好,所以用它來作爲我們一系列 Demo 的開始是非常好的選擇。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Demo02: 從零搭建一個React應用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 Demo02 中,我們會做一件與 Electron 無關事情 —— 從零搭建一個 React 應用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在這個 React 應用中,我們將支持 TypeScript、Scss、熱更新。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"雖然說有 "},{"type":"codeinline","content":[{"type":"text","text":"create-react-app"}]},{"type":"text","text":" 這樣的的官方腳手架可以快速搭建項目,但是從零搭建可以將項目儘可能的在自己的掌控範圍之內。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於這個 Demo 與 本教程的主題無關,所以這邊就不展開講了,只展現一下 Demo 的目錄結構:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"demo\n└─src\n ├─index.tsx\n └─container\n └─App\n ├─index.tsx\n └─index.scss\n├─index.html\n├─package.json\n├─tsconfig.json\n└─webpack.config.js"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對這個 Demo 感興趣的同學可以查看"},{"type":"link","attrs":{"href":"https://github.com/WangYuLue/electron-demos/tree/master/demo02","title":""},"content":[{"type":"text","text":"Demo02代碼"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以下是這個 Demo 必要的相關依賴安裝:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"bash"},"content":[{"type":"text","text":"# 安裝 webpack 相關依賴\nyarn add webpack webpack-cli webpack-dev-server -D\nyarn add html-webpack-plugin -D \n# 安裝 typescript 相關依賴\nyarn add typescript ts-loader -D\n# 安裝 react 相關依賴\nyarn add react react-dom\nyarn add @types/react @types/react-dom -D\n# 安裝 scss 相關依賴\nyarn add sass-loader node-sass -D\nyarn add style-loader css-loader -D"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Demo03: 將 Electron 與 React 結合"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"衆所周知,前端項目在瀏覽器運行,而 Electron 是在桌面環境中運行。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 Demo03 中,我們將嘗試在 Electron 運行 React 項目。在開發環境中,Electron 將引用 React 開發環境下的 URL,以保證獲得 React 熱更新的能力,這也是我們在這個 Demo 中要做的事情。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在下一個 Demo 中,我們還會講到在 Electron 打包後,Electron 將引用 React 打包後的文件,以獲得更好的性能。我們先來看這個 Demo。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,拷貝 Demo02 文件夾,將其改名爲 Demo03,並進入 Demo03:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、將 Demo01 中的 "},{"type":"codeinline","content":[{"type":"text","text":"main.js"}]},{"type":"text","text":" 也拷貝過來,將 "},{"type":"codeinline","content":[{"type":"text","text":"main.js"}]},{"type":"text","text":" 中的 "},{"type":"codeinline","content":[{"type":"text","text":"createWindow"}]},{"type":"text","text":" 修改如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"diff"},"content":[{"type":"text","text":" function createWindow() {\n // 創建瀏覽器窗口\n let win = new BrowserWindow({\n width: 800,\n height: 600,\n webPreferences: {\n nodeIntegration: true\n }\n })\n\n+ win.loadURL('http://localhost:3000')\n- win.loadFile('index.html')\n }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這樣 Electron 就可以加載 React 開發環境項目了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、在 "},{"type":"codeinline","content":[{"type":"text","text":"package.json"}]},{"type":"text","text":" 中將 "},{"type":"codeinline","content":[{"type":"text","text":"script"}]},{"type":"text","text":"改成:"}]},{"type":"codeblock","attrs":{"lang":"js"},"content":[{"type":"text","text":"{\n \"start-electron\": \"../node_modules/.bin/electron .\",\n \"start\": \"../node_modules/.bin/webpack-dev-server --config webpack.config.js\"\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中 "},{"type":"codeinline","content":[{"type":"text","text":"start"}]},{"type":"text","text":" 啓動 React 項目,"},{"type":"codeinline","content":[{"type":"text","text":"start-electron"}]},{"type":"text","text":" 啓動 Electron 項目。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、在 "},{"type":"codeinline","content":[{"type":"text","text":"webpack.config.js"}]},{"type":"text","text":" 中的 "},{"type":"codeinline","content":[{"type":"text","text":"devServer"}]},{"type":"text","text":" 裏添加 "},{"type":"codeinline","content":[{"type":"text","text":"after"}]},{"type":"text","text":" 鉤子函數,以便在運行 react 項目後拉起 electron 項目:"}]},{"type":"codeblock","attrs":{"lang":"js"},"content":[{"type":"text","text":"{\n after() {\n spawn('npm', ['run', 'start-electron'], {\n shell: true,\n env: process.env,\n stdio: 'inherit'\n })\n .on('close', code => process.exit(code))\n .on('error', spawnError => console.error(spawnError));\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"經過以上配置後,運行 "},{"type":"codeinline","content":[{"type":"text","text":"yarn start"}]},{"type":"text","text":" 就可以同時把 React 項目 和 Electron 都啓動起來了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Demo03 詳細的代碼可以"},{"type":"link","attrs":{"href":"https://github.com/WangYuLue/electron-demos/tree/master/demo03","title":""},"content":[{"type":"text","text":"戳這裏"}]},{"type":"text","text":"查看。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Demo04: 打包 Electron 應用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在上面的Demo中,我們簡單搭建了開發環境的項目配置,但是讀者的心裏可能還沒底,它在打包後還能正常運行嗎?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有過前端開發經驗的同學就會知道,很多時候,明明開發環境項目運行的很好,但是一打包之後就出問題了。不是路徑引用錯誤就是 找不到 icon。所以,爲了打消同學們的顧慮,我們將在 Demo04 中實踐如何打包 Demo03 中的項目。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,拷貝 Demo03 文件夾,將其改名爲 Demo04,並進入 Demo04:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在開始之前,筆者先簡單介紹一下 Electron 主流的兩款打包工具 "},{"type":"link","attrs":{"href":"https://github.com/electron/electron-packager","title":""},"content":[{"type":"text","text":"electron-packager"}]},{"type":"text","text":" 和 [electron-builder](https://github.com/electron-userland/electron-builder)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"electron-builder"}]},{"type":"text","text":" 在社區相對更加活躍,而且筆者項目實際開發中用的也是 "},{"type":"codeinline","content":[{"type":"text","text":"electron-builder"}]},{"type":"text","text":" ,於是我們在這個demo中也用 "},{"type":"codeinline","content":[{"type":"text","text":"electron-builder"}]},{"type":"text","text":" 來打包 Electron。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、由於打包需要當前目錄有 Electron 可執行文件,所以所以首先安裝 Electron"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"yarn add [email protected] -D"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、在 "},{"type":"codeinline","content":[{"type":"text","text":"package.json"}]},{"type":"text","text":" 中加入 "},{"type":"codeinline","content":[{"type":"text","text":"build"}]},{"type":"text","text":" 字段,這個字段會告訴 "},{"type":"codeinline","content":[{"type":"text","text":"electron-builder"}]},{"type":"text","text":" 如何來打包應用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"json"},"content":[{"type":"text","text":"\"build\": {\n \"productName\": \"electron-demos\",\n \"files\": [\n \"dist/\",\n \"main.js\"\n ],\n \"dmg\": {\n \"contents\": [\n {\n \"x\": 110,\n \"y\": 150\n },\n {\n \"x\": 240,\n \"y\": 150,\n \"type\": \"link\",\n \"path\": \"/Applications\"\n }\n ]\n },\n \"win\": {\n \"target\": [\n {\n \"target\": \"nsis\",\n \"arch\": [\n \"x64\"\n ]\n }\n ]\n },\n \"directories\": {\n \"output\": \"release\"\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中,要重點關注 "},{"type":"codeinline","content":[{"type":"text","text":"files"}]},{"type":"text","text":" 字段,它指定了打包時要包括的文件。在這個 demo 中,我們需要包括主進程的 "},{"type":"codeinline","content":[{"type":"text","text":"main.js"}]},{"type":"text","text":" 和渲染進程需要的React項目打包後的 "},{"type":"codeinline","content":[{"type":"text","text":"dist"}]},{"type":"text","text":" 文件夾。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此外,再關注一下 "},{"type":"codeinline","content":[{"type":"text","text":"directories.output"}]},{"type":"text","text":" 字段,它表示 Eletron 打包後的輸出目錄,如果不配置,默認爲 "},{"type":"codeinline","content":[{"type":"text","text":"dist"}]},{"type":"text","text":",但是這和我們 React 項目的輸出目錄衝突,所以在這裏我們改爲 "},{"type":"codeinline","content":[{"type":"text","text":"release"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關於 "},{"type":"codeinline","content":[{"type":"text","text":"electron-builder"}]},{"type":"text","text":" 的詳細配置,幹興趣的同學可以查看"},{"type":"link","attrs":{"href":"https://www.electron.build/configuration/configuration","title":""},"content":[{"type":"text","text":"文檔"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、修改 "},{"type":"codeinline","content":[{"type":"text","text":"package.json"}]},{"type":"text","text":" 中的 "},{"type":"codeinline","content":[{"type":"text","text":"scripts"}]},{"type":"text","text":" 字段。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3.1、修改 "},{"type":"codeinline","content":[{"type":"text","text":"start-electron"}]},{"type":"text","text":" 命令,通過 "},{"type":"codeinline","content":[{"type":"text","text":"corss-env"}]},{"type":"text","text":" 爲其添加 "},{"type":"codeinline","content":[{"type":"text","text":"ENV"}]},{"type":"text","text":" 環境變量:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"json"},"content":[{"type":"text","text":"\"start-electron\": \"../node_modules/.bin/cross-env ENV=development ../node_modules/.bin/electron .\","}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這麼做是因爲 Electron 接下來要通過這個環境變量來判斷此時是開發環境還是生產環境,從而做出不同的行爲。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3.2、添加 "},{"type":"codeinline","content":[{"type":"text","text":"build-render"}]},{"type":"text","text":" 命令:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"json"},"content":[{"type":"text","text":"\"build-render\": \"../node_modules/.bin/webpack --config webpack.config.js\""}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"它會打包 React 項目,並且在當前目錄下生成 dist 輸出文件。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3.3、添加 "},{"type":"codeinline","content":[{"type":"text","text":"build-electron"}]},{"type":"text","text":" 命令:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"json"},"content":[{"type":"text","text":"\"build-electron\": \"../node_modules/.bin/electron-builder build -mwl\","}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"它會讀取 "},{"type":"codeinline","content":[{"type":"text","text":"package.json"}]},{"type":"text","text":" 下 "},{"type":"codeinline","content":[{"type":"text","text":"build"}]},{"type":"text","text":" 字段中的配置,並打包 Electron 項目,然後在當前目錄下生成 release 輸出文件。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"命令中的 "},{"type":"codeinline","content":[{"type":"text","text":"-mwl"}]},{"type":"text","text":" 表示打包 "},{"type":"codeinline","content":[{"type":"text","text":"mac"}]},{"type":"text","text":"、"},{"type":"codeinline","content":[{"type":"text","text":"windows"}]},{"type":"text","text":"、"},{"type":"codeinline","content":[{"type":"text","text":"linux"}]},{"type":"text","text":" 三平臺。如果讀者只想打包一個平臺的包,比如 Mac 版的,可以改成 "},{"type":"codeinline","content":[{"type":"text","text":"-m"}]},{"type":"text","text":"。 "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3.4、添加 "},{"type":"codeinline","content":[{"type":"text","text":"build"}]},{"type":"text","text":" 命令:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"json"},"content":[{"type":"text","text":"\"build\": \"npm install && npm run build-render && npm run build-electron\""}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"build"}]},{"type":"text","text":" 命令很簡單,它將安裝依賴、打包 React 項目、打包 Electron 項目結合在以前,這樣的話,我們只要運行 "},{"type":"codeinline","content":[{"type":"text","text":"yarn build"}]},{"type":"text","text":" 就能成功打包 Electron 了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4、上面提到,由於此時demo要兼顧開發環境和生產環境,在開發環境中,Electron 要引用 React 開發環境下的 URL,以獲得 React 熱更新的能力。在生產環境中,Electron 要引用 React 打包後的文件。所以,我們要對 "},{"type":"codeinline","content":[{"type":"text","text":"mian.js"}]},{"type":"text","text":" 做一些微小的改造。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"diff"},"content":[{"type":"text","text":" const { app, BrowserWindow } = require('electron')\n+ const path = require('path');\n\n+ const isDev = process.env.ENV === 'development';\n\n function createWindow() {\n // 創建瀏覽器窗口\n let win = new BrowserWindow({\n width: 800,\n height: 600,\n webPreferences: {\n nodeIntegration: true\n }\n })\n\n- win.loadURL('http://localhost:3000')\n\n+ if (isDev) {\n+ win.loadURL(`http://localhost:3000`);\n+ } else {\n+ win.loadFile(path.resolve(__dirname, './dist/index.html'));\n+ }\n }\n\n app.whenReady().then(createWindow)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們可以看到,此時我們根據此時的環境來加載不同的資源,開發環境加載 "},{"type":"codeinline","content":[{"type":"text","text":"http://localhost:3000"}]},{"type":"text","text":",生產環境加載打包後的文件。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"經過以上配置後,運行 "},{"type":"codeinline","content":[{"type":"text","text":"yarn build"}]},{"type":"text","text":" 我們就可以打包 Electron 項目了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Demo04 詳細的代碼可以"},{"type":"link","attrs":{"href":"https://github.com/WangYuLue/electron-demos/tree/master/demo04","title":""},"content":[{"type":"text","text":"戳這裏"}]},{"type":"text","text":"查看。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Demo05: 實際開發一個小 Demo"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面的四個 Demo 中,我已經體驗了從零開始Electron項目到成功打包一個Electron的完整過程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是我們上面做的無非是在Eletron中套一個可以在瀏覽器中跑的項目,到目前爲止,我們還沒有體驗到 Electron 其他能力,例如:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"調用 Node.js 的 API(如文件讀寫)"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"調用操作系統本地功能的 API(如打開文件窗口、通知)"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 Demo05 中,筆者將帶領讀者完成一個非常簡單的文件讀寫應用,它將支持以下功能:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"列出指定目錄下的文件列表"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"支持在指定目錄中添加文件"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"文件添加成功後調用系統的通知功能"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此外,在這個 Demo 中,我們還會測試主進程與渲染進程之間的通信功能。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"需要注意的是,Demo05的 React 項目將無法在瀏覽器上運行,因爲此時 React 會有很多 node 代碼,而瀏覽器中並沒有對應的 API。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,拷貝 Demo04 文件夾,將其改名爲 Demo05,並進入 Demo05:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、由於接下來會在我們的 React 項目中加入大量的 node 代碼,比如 "},{"type":"codeinline","content":[{"type":"text","text":"require('fs')"}]},{"type":"text","text":",這樣的話原來 React 的 webpack 配置運行後肯定會報錯,幸運的是,webpack 貼心的爲我們準備了 Electron 的相關配置項。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們可以在 "},{"type":"codeinline","content":[{"type":"text","text":"webpack.config.js"}]},{"type":"text","text":" 中的裏添加 "},{"type":"codeinline","content":[{"type":"text","text":"target"}]},{"type":"text","text":" 字段,以表示接下來 React 的運行環境將在 Electron 的 render 進程中:"}]},{"type":"codeblock","attrs":{"lang":"js"},"content":[{"type":"text","text":"{\n target: 'electron-renderer'\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關於 "},{"type":"codeinline","content":[{"type":"text","text":"webpack"}]},{"type":"text","text":" 的 "},{"type":"codeinline","content":[{"type":"text","text":"target"}]},{"type":"text","text":" 字段的配置,感興趣的同學可以閱讀"},{"type":"link","attrs":{"href":"https://webpack.js.org/configuration/target/","title":""},"content":[{"type":"text","text":"官方文檔"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、接下來我們將在 React 項目中添加一個組件,用它來查看文件列表並添加新的文件:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 Demo05 中新建 "},{"type":"codeinline","content":[{"type":"text","text":"src/container/file-list"}]},{"type":"text","text":" 目錄,並添加 "},{"type":"link","attrs":{"href":"https://github.com/WangYuLue/electron-demos/blob/master/demo05/src/container/file-list/index.tsx","title":""},"content":[{"type":"text","text":"index.tsx"}]},{"type":"text","text":" 文件:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"tsx"},"content":[{"type":"text","text":"import React, { Component } from 'react';\nimport { remote, OpenDialogReturnValue } from 'electron';\nimport './index.scss';\n\nconst path = require(\"path\");\nconst fs = require('fs');\nconst { dialog, Notification } = remote;\n\nconst readDistFiles = (path: string, callBack: (data: string[]) => void) => {\n fs.readdir(path, (err: any, files: any) => {\n const data: string[] = [];\n files.forEach((file: any) => {\n console.log(file);\n data.push(file);\n });\n if (callBack) {\n callBack(data);\n }\n })\n}\n\ninterface IState {\n path: string;\n data: string[];\n addFileName: string;\n addFileContent: string;\n}\n\nclass FileList extends Component {\n constructor(props: any) {\n super(props);\n this.state = {\n path: '',\n data: [],\n addFileName: '',\n addFileContent: '',\n }\n }\n\n onChooseFile = () => {\n dialog.showOpenDialog({\n properties: ['openDirectory']\n }).then((res: OpenDialogReturnValue) => {\n const filenames = res.filePaths;\n if (filenames && filenames.length > 0) {\n this.setState({ path: filenames[0] })\n readDistFiles(filenames[0], (data: string[]) => {\n console.log('data', data);\n this.setState({ data })\n })\n }\n });\n }\n\n onAppendFile = (name: string, content: string) => {\n fs.appendFile(path.resolve(this.state.path, name), content, (err: any) => {\n if (err) throw err;\n console.log('Saved!');\n let myNotification = new Notification({ title: '渲染進程通知', body: '新文件添加成功' });\n myNotification.show();\n readDistFiles(this.state.path, (data: string[]) => {\n this.setState({ data })\n })\n });\n }\n\n render() {\n return (\n \n \n
當前文件夾路徑:{this.state.path}
\n
文件夾下文件列表:
\n
\n {\n this.state.data.map(file => {\n return (\n
\n {file}\n
\n )\n })\n }\n
\n
\n
\n 文件名稱:\n this.setState({ addFileName: e.target.value })} \n />\n
\n
\n 文件內容:\n this.setState({ addFileContent: e.target.value })} \n />\n
\n \n
\n
\n )\n }\n}\n\nexport default FileList;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面的組件比較簡單,我們可以看到,在選擇文件夾時,會調用 "},{"type":"codeinline","content":[{"type":"text","text":"remote.dialog.showOpenDialog"}]},{"type":"text","text":", 它會打開系統的文件窗口。然後,我們可以用 node 的 "},{"type":"codeinline","content":[{"type":"text","text":"fs"}]},{"type":"text","text":" 模塊來寫入或者讀取文件。在讀取成功後,我們還可以通過"},{"type":"codeinline","content":[{"type":"text","text":"remote"}]},{"type":"text","text":" 的 "},{"type":"codeinline","content":[{"type":"text","text":"Notification"}]},{"type":"text","text":" 來調用系統的通知功能。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"總的來說,在前端項目中調用 node 相關的模塊,體驗很奇妙。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、通過 "},{"type":"codeinline","content":[{"type":"text","text":"ipcMain"}]},{"type":"text","text":" 和 "},{"type":"codeinline","content":[{"type":"text","text":"ipcRenderer"}]},{"type":"text","text":" 我們可以實現渲染進程與主進程之間的通信。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ipcMain 在主進程中使用,用來處理渲染進程(網頁)發送的同步和異步的信息:"}]},{"type":"codeblock","attrs":{"lang":"js"},"content":[{"type":"text","text":"const {ipcMain} = require('electron')\n\n// 監聽渲染程序發來的事件\nipcMain.on('something', (event, data) => {\n event.sender.send('something1', '我是主進程返回的值')\n})"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ipcRenderer 在渲染進程中使用,用來發送同步或異步的信息給主進程,也可以用來接收主進程的回覆信息。"}]},{"type":"codeblock","attrs":{"lang":"js"},"content":[{"type":"text","text":"const { ipcRenderer} = require('electron') \n\n// 發送事件給主進程\nipcRenderer.send('something', '傳輸給主進程的值') \n\n// 監聽主進程發來的事件\nipcRenderer.on('something1', (event, data) => {\n console.log(data) // 我是主進程返回的值\n})"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當然,我們還可以在 Render 進程中直接使用 "},{"type":"link","attrs":{"href":"https://electronjs.org/docs/api/remote","title":""},"content":[{"type":"text","text":"remote"}]},{"type":"text","text":" 模塊, 這樣的話就可以直接調用 main 進程對象的方法, 而不必顯式發送進程間消息。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"js"},"content":[{"type":"text","text":"const { dialog } = require('electron').remote\ndialog.showMessageBox({type: 'info', message: '在渲染進程中直接使用主進程的模塊'})"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"經過以上改造,運行 "},{"type":"codeinline","content":[{"type":"text","text":"yarn start"}]},{"type":"text","text":" 我們就可以體驗真正意義上的桌面應用了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Demo05 詳細的代碼可以"},{"type":"link","attrs":{"href":"https://github.com/WangYuLue/electron-demos/tree/master/demo05","title":""},"content":[{"type":"text","text":"戳這裏"}]},{"type":"text","text":"查看。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Demo06: 在主進程中使用 Typescript"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在之前的 Demo 中,我們會發現,在渲染進程中,我們已經用上來 TypeSctipt。但是在主進程中,用的依舊是 javascript。考慮將來項目會越來越大,爲了保證項目的可靠性,在這個 demo 中,我們會將主進程也改造成 Typescipt。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,拷貝 Demo05 文件夾,將其改名爲 Demo06,並進入 Demo06:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、新建 "},{"type":"codeinline","content":[{"type":"text","text":"webpack.main.config.js"}]},{"type":"text","text":" 文件,之後我們會用這個文件的 wabpack 配置來打包主進程的代碼,配置如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"js"},"content":[{"type":"text","text":"const path = require('path');\n\nmodule.exports = {\n target: 'electron-main',\n mode: 'development',\n entry: './main.ts',\n output: {\n filename: './main.js',\n path: path.resolve(__dirname, '')\n },\n module: {\n rules: [\n {\n test: /\\.ts$/,\n use: 'ts-loader',\n exclude: /node_modules/\n }\n ]\n },\n resolve: {\n extensions: ['.ts', '.js'],\n },\n node: {\n __dirname: false,\n __filename: false\n },\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這是一段很簡單的 webpack 配置,其中主要注意兩點:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1.1、需要將 "},{"type":"codeinline","content":[{"type":"text","text":"target"}]},{"type":"text","text":" 配置爲 "},{"type":"codeinline","content":[{"type":"text","text":"electron-main"}]},{"type":"text","text":" 表示以接下來 打包的代碼將在 Electron 的 mian 進程中執行"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1.2、由於 webpack 會對 "},{"type":"codeinline","content":[{"type":"text","text":"__dirname"}]},{"type":"text","text":" 和 "},{"type":"codeinline","content":[{"type":"text","text":"__filename"}]},{"type":"text","text":" 做其他額外的處理,爲了保證 "},{"type":"codeinline","content":[{"type":"text","text":"__dirname"}]},{"type":"text","text":"、"},{"type":"codeinline","content":[{"type":"text","text":"__filename"}]},{"type":"text","text":" 的行爲和在 node 中保持一致,添加如下配置:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"js"},"content":[{"type":"text","text":"node: {\n __dirname: false,\n __filename: false\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果不這樣配置,打包後 "},{"type":"codeinline","content":[{"type":"text","text":"__dirname"}]},{"type":"text","text":" 和 "},{"type":"codeinline","content":[{"type":"text","text":"__filename"}]},{"type":"text","text":" 將都是 "},{"type":"codeinline","content":[{"type":"text","text":"/"}]},{"type":"text","text":";"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、將 "},{"type":"codeinline","content":[{"type":"text","text":"webpack.config.js"}]},{"type":"text","text":" 改名爲 "},{"type":"codeinline","content":[{"type":"text","text":"webpack.renderer.config.js"}]},{"type":"text","text":",用來和 "},{"type":"codeinline","content":[{"type":"text","text":"webpack.main.config.js"}]},{"type":"text","text":" 保持對應。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、修改 "},{"type":"codeinline","content":[{"type":"text","text":"package.json"}]},{"type":"text","text":" 中的 "},{"type":"codeinline","content":[{"type":"text","text":"script"}]},{"type":"text","text":" 字段:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"diff"},"content":[{"type":"text","text":" {\n- \"start-electron\": \"../node_modules/.bin/cross-env NODE_ENV=development ../node_modules/.bin/electron .\",\n+ \"start-electron\": \"npm run build-main && ../node_modules/.bin/cross-env ENV=development ../node_modules/.bin/electron .\",\n- \"start\": \"../node_modules/.bin/webpack-dev-server --config webpack.config.js\",\n+ \"start\": \"../node_modules/.bin/webpack-dev-server --config webpack.renderer.config.js\",\n- \"build-render\": \"../node_modules/.bin/webpack --config webpack.config.js\",\n+ \"build-render\": \"../node_modules/.bin/webpack --config webpack.renderer.config.js\",\n+ \"build-main\": \"../node_modules/.bin/webpack --config webpack.main.config.js\",\n \"build-electron\": \"../node_modules/.bin/electron-builder build -mwl\",\n- \"build\": \"npm install && npm run build-render && npm run build-electron\"\n+ \"build\": \"npm install && npm run build-render && npm run build-main && npm run build-electron\"\n }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中注意兩點:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3.1、"},{"type":"codeinline","content":[{"type":"text","text":"start-electron"}]},{"type":"text","text":" 將在運行 "},{"type":"codeinline","content":[{"type":"text","text":"electron ."}]},{"type":"text","text":" 先打包主進程的 typescrip 代碼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3.2、"},{"type":"codeinline","content":[{"type":"text","text":"build"}]},{"type":"text","text":" 執行後將先打包渲染進程,再打包主進程,最後再打包整個 Electron 應用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"經過以上改造,主進程也改造成 typescript 了,項目的可靠性大大增強。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Demo06 詳細的代碼可以"},{"type":"link","attrs":{"href":"https://github.com/WangYuLue/electron-demos/tree/master/demo06","title":""},"content":[{"type":"text","text":"戳這裏"}]},{"type":"text","text":"查看。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"經過以上 6 個 demo 的學習,同學們已經有能力搭建一個完整的 Electron 應用。但是我們還有一些進階的用法,感興趣的同學可以繼續往下閱讀。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Demo07: 主進程監聽文件變化並重啓"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前面的開發中我們發現,渲染進程修改後可以自動刷新,而主進程卻不行,這可能會影響到我們的開發效率。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以,在 Demo07 中,通過 "},{"type":"codeinline","content":[{"type":"text","text":"nodemon"}]},{"type":"text","text":",我們將主進程也改造成修改後可以自動刷新。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,拷貝 Demo06 文件夾,將其改名爲 Demo07,並進入 Demo07:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、安裝 "},{"type":"codeinline","content":[{"type":"text","text":"nodemon"}]},{"type":"text","text":":"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"bash"},"content":[{"type":"text","text":"yarn add nodemon -D"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、在 "},{"type":"codeinline","content":[{"type":"text","text":"package.json"}]},{"type":"text","text":" 添加一行腳本:"}]},{"type":"codeblock","attrs":{"lang":"js"},"content":[{"type":"text","text":"{\n \"start-electron-with-nodemon\": \"nodemon --watch main.ts --exec 'npm run start-electron'\",\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們之後會通過 "},{"type":"codeinline","content":[{"type":"text","text":"nodemon"}]},{"type":"text","text":" 來啓動 Electron,它將監聽 "},{"type":"codeinline","content":[{"type":"text","text":"main.ts"}]},{"type":"text","text":" 的文件變化。如果發生變化,則會從新運行 "},{"type":"codeinline","content":[{"type":"text","text":"start-electron"}]},{"type":"text","text":" 命令。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、將 "},{"type":"codeinline","content":[{"type":"text","text":"webpack.renderer.config.js"}]},{"type":"text","text":" 中 "},{"type":"codeinline","content":[{"type":"text","text":"devServer"}]},{"type":"text","text":" 的 after 鉤子中的 "},{"type":"codeinline","content":[{"type":"text","text":"start-electron"}]},{"type":"text","text":" 改爲 "},{"type":"codeinline","content":[{"type":"text","text":"start-electron-with-nodemon"}]},{"type":"text","text":":"}]},{"type":"codeblock","attrs":{"lang":"diff"},"content":[{"type":"text","text":" devServer: {\n port: 3000,\n after() {\n+ spawn('npm', ['run', 'start-electron-with-nodemon'], {\n- spawn('npm', ['run', 'start-electron'], {\n shell: true,\n env: process.env,\n stdio: 'inherit'\n })\n .on('close', code => process.exit(code))\n .on('error', spawnError => console.error(spawnError));\n }\n }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"經過以上的簡單配置,我們的主進程也能修改文件後自動刷新了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Demo07 詳細的代碼可以"},{"type":"link","attrs":{"href":"https://github.com/WangYuLue/electron-demos/tree/master/demo07","title":""},"content":[{"type":"text","text":"戳這裏"}]},{"type":"text","text":"查看。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Demo08: 在 vscode 中調試主進程和渲染進程"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"任何時候,如果代碼出了 bug,我們的第一反應是打印 log 看一下出了什麼問題。但是這樣的調試方式相對低效,有些時候,我們需要借用編輯器的調試功能來幫助我們調試代碼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在這個 Demo 中,筆者將嘗試用 vscode 自帶的調試工具來調試 electron 的主進程和渲染進程;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於 vscode 中的調試配置項相對較多,所以強烈建議大家先看一遍 vscode 調試的"},{"type":"link","attrs":{"href":"https://code.visualstudio.com/docs/nodejs/nodejs-debugging","title":""},"content":[{"type":"text","text":"官方文檔"}]},{"type":"text","text":",或者看一下 github 上 [Electron調試實際案例](https://github.com/Microsoft/vscode-recipes/tree/master/Electron)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文的配置與上面提到的"},{"type":"link","attrs":{"href":"https://github.com/Microsoft/vscode-recipes/tree/master/Electron","title":""},"content":[{"type":"text","text":"實際案例"}]},{"type":"text","text":"有一些差異,因爲我們 demo 開發環境的 render 進程是用一個 web server 啓動的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,拷貝 Demo07 文件夾,將其改名爲 Demo08,並進入 Demo08:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、在 "},{"type":"codeinline","content":[{"type":"text","text":"webpack.main.config.js"}]},{"type":"text","text":" 和 "},{"type":"codeinline","content":[{"type":"text","text":"webpack.render.config.js"}]},{"type":"text","text":" 中添加 "},{"type":"codeinline","content":[{"type":"text","text":"devtool: 'source-map'"}]},{"type":"text","text":"。因爲主進程和渲染進程都是用 "},{"type":"codeinline","content":[{"type":"text","text":"typescript"}]},{"type":"text","text":" 寫的,需要在打包時生成 "},{"type":"codeinline","content":[{"type":"text","text":"source maps"}]},{"type":"text","text":" 以形成映射,才能在 "},{"type":"codeinline","content":[{"type":"text","text":"typescript"}]},{"type":"text","text":" 文件中正確的調試代碼。詳情可以查看"},{"type":"link","attrs":{"href":"https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_source-maps","title":""},"content":[{"type":"text","text":"官方文檔"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、改造"},{"type":"codeinline","content":[{"type":"text","text":"webpack.renderer.config.js"}]},{"type":"text","text":",將 "},{"type":"codeinline","content":[{"type":"text","text":"devServer"}]},{"type":"text","text":" 的 after 鉤子函數。"}]},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":" devServer: {\n port: 3000,\n after() {\n- spawn('npm', ['run', 'start-electron-with-nodemon'], {\n+ spawn('npm', ['run', 'build-main'], {\n shell: true,\n env: process.env,\n stdio: 'inherit'\n })\n- .on('close', code => process.exit(code))\n .on('error', spawnError => console.error(spawnError));\n }\n }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這也就意味着運行渲染進程後,只會將主進程的代碼打包一下,而不再將主進程啓動起來。因爲在稍後的調試中我們會在 vscode 的 "},{"type":"codeinline","content":[{"type":"text","text":"launch.json"}]},{"type":"text","text":" 中啓動主進程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、運行"},{"type":"codeinline","content":[{"type":"text","text":"yarn start"}]},{"type":"text","text":",啓動渲染進程,並且給主進程打包。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4、在當前項目目錄下創建一個 "},{"type":"codeinline","content":[{"type":"text","text":".vscode"}]},{"type":"text","text":" 目錄,並且在該目錄下創建一個 "},{"type":"codeinline","content":[{"type":"text","text":"launch.json"}]},{"type":"text","text":" 文件,在該文件裏添加如下配置:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"json{\n \"version\": \"0.2.0\",\n \"configurations\": [\n {\n \"type\": \"node\",\n \"request\": \"launch\",\n \"name\": \"Electron: Main\",\n \"protocol\": \"inspector\",\n \"runtimeExecutable\": \"${workspaceFolder}/node_modules/.bin/electron\",\n \"runtimeArgs\": [\n \"--remote-debugging-port=9233\",\n \"./demo08/main.js\"\n ]\n },\n {\n \"type\": \"chrome\",\n \"request\": \"attach\",\n \"name\": \"Electron: Renderer\",\n \"port\": 9233,\n \"url\": \"http://localhost:3000\",\n \"webRoot\": \"${workspaceFolder}/demo08\",\n },\n ],\n \"compounds\": [\n {\n \"name\": \"Electron: All\",\n \"configurations\": [\n \"Electron: Main\",\n \"Electron: Renderer\"\n ]\n }\n ]\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們可以看到,在 "},{"type":"codeinline","content":[{"type":"text","text":"configurations"}]},{"type":"text","text":" 中有兩個對象:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中 \"Electron: Main\" 表示的是對主進程的調試,它的 "},{"type":"codeinline","content":[{"type":"text","text":"request"}]},{"type":"text","text":" 爲 "},{"type":"codeinline","content":[{"type":"text","text":"launch"}]},{"type":"text","text":",表示在調試的時候啓動主進程。我們還可以通過 "},{"type":"codeinline","content":[{"type":"text","text":"env"}]},{"type":"text","text":" 來傳入環境變量。它還會暴露一個 "},{"type":"codeinline","content":[{"type":"text","text":"--remote-debugging-port"}]},{"type":"text","text":" 的遠程調試端口,這個端口很重要,因爲在接下來調試渲染進程時會用到它。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"\"Electron: Renderer\" 表示的是對渲染進程的調試,它的 "},{"type":"codeinline","content":[{"type":"text","text":"request"}]},{"type":"text","text":" 爲 "},{"type":"codeinline","content":[{"type":"text","text":"attach"}]},{"type":"text","text":",表示只是連接到正在調試的進程。而它的 "},{"type":"codeinline","content":[{"type":"text","text":"port"}]},{"type":"text","text":" 剛好是 "},{"type":"codeinline","content":[{"type":"text","text":"--remote-debugging-port"}]},{"type":"text","text":" 暴露出來的端口,"},{"type":"codeinline","content":[{"type":"text","text":"url"}]},{"type":"text","text":" 則是本地渲染進程的地址。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接着,我們會看到 "},{"type":"codeinline","content":[{"type":"text","text":"compounds"}]},{"type":"text","text":",它的作用很簡單,就是把主進程調試和渲染進程調試結合起來。當調試時選擇 "},{"type":"codeinline","content":[{"type":"text","text":"Electron: All"}]},{"type":"text","text":" 時,就可以把 \"Electron: Main\" 和 \"Electron: Renderer\" 都拉起來。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這一塊的配置比較多,其中每個配置的作用可以參考 vscode 的"},{"type":"link","attrs":{"href":"https://code.visualstudio.com/docs/nodejs/nodejs-debugging","title":""},"content":[{"type":"text","text":"官方文檔"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"5、點擊 vscode 自帶的調試按鈕,選擇 "},{"type":"text","marks":[{"type":"strong"}],"text":"Electron: All"},{"type":"text","text":",就可以將 electron 啓動起來了,這時候在主進程的 "},{"type":"codeinline","content":[{"type":"text","text":".ts"}]},{"type":"text","text":" 文件和渲染進程的 "},{"type":"codeinline","content":[{"type":"text","text":".ts"}]},{"type":"text","text":" 文件中打斷點就可以發現都能起作用了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":">注意,實際測試發現,主進程的調試需要在打包後的 "},{"type":"codeinline","content":[{"type":"text","text":"main.js"}]},{"type":"text","text":" 中打一次斷點,然後在通過 "},{"type":"codeinline","content":[{"type":"text","text":"source map"}]},{"type":"text","text":" 和 js 文件生成的只讀的 typescipt 文件中打斷點才能順利調試。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"經過以上的配置,我們可以順利的在主進程和渲染進程中調試代碼了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Demo08 詳細的代碼可以"},{"type":"link","attrs":{"href":"https://github.com/WangYuLue/electron-demos/tree/master/demo08","title":""},"content":[{"type":"text","text":"戳這裏"}]},{"type":"text","text":"查看。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"總結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過以上的一系列 Demo,我們重零搭建一個完整的 Electron 應用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們瞭解了 Electron的核心知識點、搭建一個最簡單的 Electron,將 Electron 和前端應用相結合,配置 TypeScript 以保證代碼質量,跨平臺打包 Electron 應用以及 如何調試 Electron。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這些 demo 的完整代碼可以"},{"type":"link","attrs":{"href":"https://github.com/WangYuLue/electron-demos","title":""},"content":[{"type":"text","text":"點這裏"}]},{"type":"text","text":"查看,如果感覺這些 demo 寫的不錯,可以給筆者一個 star,謝謝大家閱讀"}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章