從零到一,用 Electron 開發桌面效率工具
Electron 已經不算新技術,最早是 github 從 Atom 編輯器衍生出來的框架。通過編寫 Javascript, HTML, CSS 能快速編譯出跨系統的桌面 app。Electron 的出現使得作為前端開發工程師的我們輸出範圍更廣。
分享最近用 Electron 做的一個基於番茄工作法的小應用,由於實現難度不大,市面上已經有非常多類似的app。我們嘗試用 Electron 來實現一個。
最終效果預覽:

:tomato: 工作法
番茄工作法的核心是將任務顆粒拆分到單位時間內(25分鐘)可以完成,在這25分鐘內專注在這個任務三,不允許做任何與任務無關的事,在任務任務完成之後可以短暫休息一會,再繼續工作。
所以這個 app 的重點是讓你建立任務,:hourglass_flowing_sand: 25分鐘,幫讓 focus on 當前在做的任務。
站在巨人的肩膀上開發
嘗試新技術的時候,不要從零開始學習如何搭建技術棧,先做出來,遇到問題再查。Electron 社群有很多優秀的沉澱,工具,模板,元件,教程等等。
搜尋 react 關鍵字,找到了 electron-react-boilerplate 這個樣板庫, 這個庫已經集成了 react
, redux
, sass
, flow
, hmr
webpack
等工具,同時準備好 electron-builder
打包工具,作為 electron
新手,我們優先選擇開箱即用的工具,快速開啟業務開發。
SVG 和 React Component
大概畫了一下草圖,準備進入開發階段。考慮後面會用到 svg icon,先在FlatIcon 上找些免費的圖示,下載 SVG 檔案。
通過SVGR 線上工具匯入 svg 內容生成 React Component 程式碼。(svgr 也有 cli 等工具)
用 SVG Component 的好處是可以在程式碼上更靈活地控制樣式,相比 png 圖示可互動性強,複用率高。

托盤和托盤彈窗
這個 app 啟動的時候就隱藏在托盤選單的一角,點選的時候顯示 BrowserWindow,通過 Electron 提供的方法,可以獲得托盤和托盤彈窗的 Bounds 資訊,設定座標位置。
// main.js const tray = new Tray(path.join(__dirname, '../static', 'tray.png')); const mainWindow = new BrowserWindow({ // ...others frame: false, resizable: true, transparent: true }); const showWindow = () => { const { x, y } = getPositionFromActiveDisplay(); mainWindow.setPosition(x, y, true); mainWindow.show(); }; const getPositionFromActiveDisplay = () => { const trayBounds = tray.getBounds(); const windowBounds = mainWindow.getBounds(); const x = Math.round(trayBounds.x + trayBounds.width / 2 - windowBounds.width / 2); const y = Math.round(trayBounds.y + trayBounds.height); return { x, y }; }; 複製程式碼

:point_up_2:圖的三角是由前端程式碼繪製的,加上 frame 和 electron 背景色,應該長這樣。

渲染執行緒和主執行緒
app 需要倒計時功能,告訴使用者距離任務完成時間還有多久。Electron 有渲染程序和主執行緒,BrowserWindow 不可見的時候,渲染程序會盡量減少消耗,所以如果 Tick 在渲染程序的話,當 app 處於後臺時會出現非常大的時間偏差。這裡使用 Electron 提供的 ipcMain 和 ipcRenderer 做程序通訊。
在主執行緒每秒傳送 Tick 事件
// main.js ipcMain.once('store-ready', event => { const run = () => { setTimeout(() => { run(); event.sender.send('tick'); }, 1000); }; run(); }); 複製程式碼
渲染程序就收事件並將 dispatch TICK action。
// app/index.js const store = configureStore({ tasks: electronStore.getTasks() }); ipcRenderer.send('store-ready'); ipcRenderer.on('tick', () => { store.dispatch({ type: TICK }); }); 複製程式碼
redux store 裡面判斷當前執行的任務計算倒計時時間。
switch(action.type) { case TICK: return { ...state, rows: state.rows.map(task => task.id === state.currentId ? { ...task, remain: Math.max(task.remain - 1, 0) } : task ) }; 複製程式碼
資料持久儲存
資料持久化有很多種方案,因為是前端瀏覽器,我們可以選擇 localStorage, Cookie,indexDB 等等。考慮可靠性,持久化以及儲存空間,還可以通過 Electron 寫檔案的方式,把資料寫入到應用路徑下。這樣即使 app 被解除安裝了,只要資料沒被清空,使用者資料還在。
通過 Electron app getPath 可以獲得應用儲存路徑
import { app } from 'electron'; app.getPath('userData'); 複製程式碼
mac 下應用 app 的路徑是 /Users/user/Library/Application Support/focus
。更簡單的方式可以直接用開源庫 electron-store
,以 key-value
的格式儲存 json 檔案。
{ "tasks": { "rows": [ { "name": "任務名稱", "id": "91ac7f05-76f4-46ea-addb-f392a3a29b54", "created_at": 1553398427806, "plan": 1500, "remain": 0, "done": true } ], "currentId": "91ac7f05-76f4-46ea-addb-f392a3a29b54" } } 複製程式碼
倒計時 UI
有些樣式可能用 css 實現難度較大,而用 svg 的方式實現起來非常簡單。比如倒計時 UI,路徑圓角和路徑長度用 CSS 實現複雜度較高。可以在 Sketch 上直接繪製處理,匯出成 svg,直接通過 react 程式碼控制。

export default function(props: Props) { const offset = percentage * totalLength; const cx = Math.cos(percentage * Math.PI * 2 - Math.PI * 0.5) * radius + radius; const cy = Math.sin(percentage * Math.PI * 2 - Math.PI * 0.5) * radius + radius; return ( <svg> ...others <circle id="path-1" cx={cx} cy={cy} r="32" fill="white" style={{ transition: '1s linear' }} /> <path ...others strokeLinecap="round" strokeDasharray={totalLength} strokeDashoffset={offset} style={{ transition: '1s linear' }} /> </svg> ); } 複製程式碼
臨界狀態判斷
app 在任務時間結束時需要有 Notification,由於:point_up_2:的 Tick 設計,判斷任務是否完成可以放在 redux middleware 上。
// middlewares/tasks export default ({ getState }) => next => action => { if (typeof action === 'object' && action.type === 'TICK') { const beforeCount = getTimeEndTaksCount(getState); next(action); const afterCount = getTimeEndTaksCount(getState); if (beforeCount !== afterCount) { new Notification('Focus,任務完成了嗎?'); } } else { next(action); } }; 複製程式碼
經過一個 Tick action 之後,判斷任務完成數是否有變化,並使用 HTML5 Notification 通知使用者。

Travis CI
功能開發完畢之後,使用 electron-builder
進行打包釋出,構建之後推到 github release 下,使用者可以直接在這下載到最新的包。
同樣的, boilerplate
已經準備好 .travis.yml
檔案,唯一需要我們操作的是在 github.com/settings/to… 上生成 token,在www.travis-ci.org/ 構建之前配置 Environment Variables
, GH_TOKEN

tirgger build, 成功之後就能看到構建成功過的包,下載使用

總結
使用 Electron,前端開發者可以使用自己的武器構建跨系統的桌面端應用,而且不用學習其他技術,缺點是一個小小的功能打包完的體積是 70M。
這個 app 從有想法到最終完成花了一天的時間,比預期的簡單,感興趣的同學也可以自己 diy 些小玩意兒。完整的程式碼在 github 上 github.com/HelKyle/foc… ,歡迎體驗,同時也歡迎 star~
