使用 create-react-app 建立 Electron Node.js 桌面應用程式的方法

2021 更新: 我強烈建議使用 electron-react-boilerplate 取代本文所述的方法

當我在 2015 年首次使用 Electron 時,它還不清楚它在現代應用程式中如此普及,而且我對其所導致的應用程式大小感到震驚。

但是,我們可以清楚地知道 Electron 已經成為必不可少的工具,並且你的應用程式不需要讓人感到遲鈍和消耗大量記憶體,就像VS Code每天向我展示的那樣(在一部不是非常快的機器上)。

所以,這裡有一個快速入門指南,使用 create-react-app 創建一個React應用程式,並預先整合了 ESlint

如果尚未安裝 Node.js, 請先安裝

在 macOS 上:

brew install node

轉到開發資料夾

cd ~/dev

建立 React 應用程式

npx create-react-app app
cd app

加入 Electron

npm install electron
npm install --save-dev electron-builder

安裝 foreman 以允許從命令列執行應用程式

npm install foreman -g

安裝 create-react-app 的相依性

npm install

配置 eslint

.eslintrc.json

{
 "env": {
 "browser": true,
 "commonjs": true,
 "es6": true,
 "jest": true
 },
 "parserOptions": {
 "ecmaFeatures": {
 "jsx": true
 },
 "sourceType": "module"
 },
 "rules": {
 "no-const-assign": "warn",
 "no-this-before-super": "warn",
 "no-undef": "warn",
 "no-continue": "off",
 "no-unreachable": "warn",
 "no-unused-vars": "warn",
 "constructor-super": "warn",
 "valid-typeof": "warn",
 "quotes": [
 2,
 "single"
 ],
 "semi": [
 "error",
 "never"
 ]
 },
 "parser": "babel-eslint",
 "extends": "airbnb",
 "plugins": [
 "react",
 "jsx-a11y",
 "import"
 ]
}

現在添加 ESLint 和一些其輔助工具

npm install eslint eslint-config-airbnb eslint-plugin-jsx-a11y eslint-plugin-import eslint-plugin-react eslint-plugin-import

目前的檔結構如下所示:

Create React App with Electron

現在調整 package.json 檔案以添加一些 Electron 輔助工具。

檔案內容目前類似於:

{
 "name": "gitometer",
 "version": "0.1.0",
 "private": true,
 "dependencies": {
 "electron": "^1.7.5",
 "eslint": "^4.5.0",
 "eslint-config-airbnb": "^15.1.0",
 "eslint-plugin-import": "^2.7.0",
 "eslint-plugin-jsx-a11y": "^6.0.2",
 "eslint-plugin-react": "^7.3.0",
 "react": "^15.6.1",
 "react-dom": "^15.6.1",
 "react-scripts": "1.0.11"
 },
 "scripts": {
 "start": "react-scripts start",
 "build": "react-scripts build",
 "test": "react-scripts test --env=jsdom",
 "eject": "react-scripts eject"
 },
 "devDependencies": {
 "electron-builder": "^19.24.1"
 }
}

(版本會在我發布後過時,所以不要在意)

將腳本屬性清除並改為

"scripts": {
 "start": "nf start -p 3000",
 "build": "react-scripts build",
 "test": "react-scripts test --env=jsdom",
 "eject": "react-scripts eject",
 "electron": "electron .",
 "electron-start": "node src/start-react",
 "react-start": "BROWSER=none react-scripts start",
 "pack": "build --dir",
 "dist": "npm run build && build",
 "postinstall": "install-app-deps"
},

在 Windows 上,您可能需要在其中使用環境變數的 .env 文件中放置 BROWSER=none,因為環境變數在 Linux/macOS 上無法正常工作

如您所見, start 已更改為 react-start,但其餘部分保持不變,同時添加了一些 Electron 公用工具。

同樣地添加

"homepage": "./",
"main": "src/start.js",

"build": {
 "appId": "com.electron.electron-with-create-react-app",
 "win": {
 "iconUrl": "https://cdn2.iconfinder.com/data/icons/designer-skills/128/react-256.png"
 },
 "directories": {
 "buildResources": "public"
 }
}

最終結果應如下所示:

{
 "name": "gitometer",
 "version": "0.1.0",
 "private": true,
 "dependencies": {
 "electron": "^1.7.5",
 "eslint": "^4.5.0",
 "eslint-config-airbnb": "^15.1.0",
 "eslint-plugin-import": "^2.7.0",
 "eslint-plugin-jsx-a11y": "^6.0.2",
 "eslint-plugin-react": "^7.3.0",
 "react": "^15.6.1",
 "react-dom": "^15.6.1",
 "react-scripts": "1.0.11"
 },
 "devDependencies": {
 "electron-builder": "^19.24.1"
 },
 "homepage": "./",
 "main": "src/start.js",
 "scripts": {
 "start": "nf start -p 3000",
 "build": "react-scripts build",
 "test": "react-scripts test --env=jsdom",
 "eject": "react-scripts eject",
 "electron": "electron .",
 "electron-start": "node src/start-react",
 "react-start": "BROWSER=none react-scripts start",
 "pack": "build --dir",
 "dist": "npm run build && build",
 "postinstall": "install-app-deps"
 },
 "build": {
 "appId": "com.electron.electron-with-create-react-app",
 "win": {
 "iconUrl": "https://cdn2.iconfinder.com/data/icons/designer-skills/128/react-256.png"
 },
 "directories": {
 "buildResources": "public"
 }
 }
}

現在在專案根目錄中創建一個名為 Procfile 的文件,其內容如下:

react: npm run react-start
electron: npm run electron-start

配置完成!

現在讓我們開始編寫一些程式碼。

src/start.js

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

const path = require('path')
const url = require('url')

let mainWindow

function createWindow() {
 mainWindow = new BrowserWindow({
 width: 800,
 height: 600,
 webPreferences: {
 nodeIntegration: true
 }
 })

 mainWindow.loadURL(
 process.env.ELECTRON_START_URL ||
 url.format({
 pathname: path.join(__dirname, '/../public/index.html'),
 protocol: 'file:',
 slashes: true
 })
 )

 mainWindow.on('closed', () => {
 mainWindow = null
 })
}

app.on('ready', createWindow)

app.on('window-all-closed', () => {
 if (process.platform !== 'darwin') {
 app.quit()
 }
})

app.on('activate', () => {
 if (mainWindow === null) {
 createWindow()
 }
})

src/start-react.js

const net = require('net')
const childProcess = require('child_process')

const port = process.env.PORT ? process.env.PORT - 100 : 3000

process.env.ELECTRON_START_URL = `http://localhost:${port}`

const client = new net.Socket()

let startedElectron = false
const tryConnection = () => {
 client.connect({ port }, () => {
 client.end()
 if (!startedElectron) {
 console.log('starting electron')
 startedElectron = true
 const exec = childProcess.exec
 exec('npm run electron')
 }
 })
}

tryConnection()

client.on('error', () => {
 setTimeout(tryConnection, 1000)
})

啟動

就是這樣!

執行

npm start

你應該會在原生應用程式中看到 React 範例應用程式:

React Sample in Electron

感謝

本文受到 https://gist.github.com/matthewjberger/6f42452cb1a2253667942d333ff53404 的重要啟發。