使用 typescript 搭建一個基於 cra 的 electron 模板
前言
最近要寫一個 electron 專案,決定採用的技術棧是 react + typescript 。本來想著用市面上現有的模板進行搭建,比如electron-react-boilerplate
。不過後來感覺這些模板有些龐大,就自己用create-react-app
簡單搭建了個模板,寫篇文章記錄一下搭建過程。
準備工作
1.使用 cra 建立專案
npx create-react-app electron-template --typescript
複製程式碼
2.新增 electron
yarn add electron -D
複製程式碼
「注:」 因為下載源的原因,electron 經常會出現下載失敗的情況,建議更換下載源或手動下載。
「yarn
替換淘寶的下載源:」
yarn config set electron_mirror https://npm.taobao.org/mirrors/electron/
複製程式碼
3.配置入口及目錄結構
在根路徑新建一個main
目錄作為存放主程序相關程式碼,建立index.ts
檔案作為入口
import { app,BrowserWindow } from 'electron'
import path from 'path'
let win: BrowserWindow | null = null
function createWindow() {
// 建立瀏覽器視窗。
win = new BrowserWindow({
width: 800, height: 600, webPreferences: {
// 加上這個就可以在渲染程序使用 winodw.require 引入 electron 模組
nodeIntegration: true
}
})
const urlLocation = 'http://localhost:3000'
win.loadURL(urlLocation)
// 當 window 被關閉,觸發該事件
win.on('closed',() => {
win = null
})
}
app.on('ready' ,createWindow)
// 當全部視窗關閉時退出程式
app.on('window-all-closed',() => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate',() => {
if (win === null) {
createWindow()
}
})
複製程式碼
開發模式配置
一般來說主程序用 JS 進行書寫,然後直接在package.json
中配置main
就可以直接執行,不過對於一個 TypeScriot 愛好者來說,開發不使用 TypeScript 渾身難受,所以這裡我使用了webpack
對主程序程式碼打包然後再使用electron
啟動。
1.配置 webpack
❝webpack 的配置我也使用的 TypeScript 進行書寫。
❞
在這裡使用 cra 內建安裝過的babel-loader + @babel/preset-typescript
作為編譯 TypeScript的方案就行了。在根路徑建立一個.babelrc
檔案:
{
"presets": ["@babel/preset-typescript"]
}
複製程式碼
然後在根路徑下建立config
目錄,然後建立一個webpackConfig.ts
檔案作為webpack
的基本配置檔案:
// config/webpackConfig.ts
import { Configuration } from 'webpack'
import path from 'path'
class WebpackConfig implements Configuration {
// 修改 target 為 electron-main,這樣才能正確打包主程序
target: Configuration['target'] = 'electron-main'
entry: Configuration['entry'] = [path.resolve(__dirname,'../main/index.ts')]
output: Configuration['output'] = {
filename: 'main.js', path: path.resolve(__dirname,'../build')
}
resolve: Configuration['resolve'] = {
alias: {
'@': path.resolve(__dirname,'../src'), '@@': path.resolve(__dirname,'../')
}, extensions: ['.ts','.tsx','.js','.jsx','.json']
}
module: Configuration['module'] = {
rules: [
{
test: /\.tsx?$/, use: [
'babel-loader'
]
}
]
}
constructor(public mode: Configuration['mode'] = 'production') {}
}
export default WebpackConfig
複製程式碼
再建立一個啟動檔案(因為是用的 TypeScript,所以採用 api 式的啟動方式):
// config/start.ts
import webpack from 'webpack'
import WebpackConfig from './WebpackConfig'
const env =
process.env.NODE_ENV === 'development' ? 'development' : 'production'
// 建立編譯時配置
const config = new WebpackConfig(env)
function errorHandler(err: Error & { details?: string },stats: webpack.Stats) {
const info = stats.toJson()
if (err || stats.hasErrors()) {
if (err) {
console.error(err.stack || err)
if (err.details) {
console.error(err.details)
}
}
if (stats.hasWarnings()) {
console.warn(info.warnings)
}
if (stats.hasErrors()) {
console.error(info.errors)
}
return false
} else {
if (stats.hasWarnings()) {
console.warn(info.warnings)
}
return true
}
}
if (env === 'development') {
// 通過watch來實時編譯
const watching = webpack(config).watch(
{
aggregateTimeout: 300, ignored: /node_modules/
}, (err,stats) => {
const flag = errorHandler(err,stats)
if (flag) {
console.log('webpack start successfully')
} else {
watching.close(() => {})
throw Error('webpack start failed')
}
}
)
} else {
webpack(config).run((err: Error & { details?: string },stats) => {
const flag = errorHandler(err,stats)
if (flag) {
console.log('webpack build successfully')
} else {
throw Error('webpack build failed')
}
})
}
複製程式碼
2.配置啟動指令碼
已經配置好了webpack
,因為是使用的 TypeScript,所以使用ts-node
執行啟動檔案,但是由於ts-loader
執行時會預設使用專案根目錄的tsconfig.json
檔案,而 cra 本身的tsconfig.json
並不是打包成 commonjs,所以我們複製一份到 config 目錄中,修改一下module
的配置:
// config/tsconfig.json
{
"compilerOptions": {
"target": "es5", "lib": ["esnext"], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "module": "commonjs", "moduleResolution": "node", "resolveJsonModule": true, "noEmit": true, "isolatedModules": true
}
}
複製程式碼
開發模式的指令碼如下:
{
"start:render": "cross-env BROWSER=none react-scripts start", "start:watch-main": "cross-env NODE_ENV=development ts-node --project ./config/tsconfig.json ./config/start.ts", "start:main": "wait-on http://localhost:3000 && nodemon --watch ./build --exec electron .", "prestart": "rimraf ./build", "start": "npm-run-all --parallel start:*",}
複製程式碼
「配置說明:」
使用 cross-env
相容 windows 和 mac 環境下的環境變數。將 node 設定為開發環境使 webpack
實時監聽主程序相關檔案變化重新打包。使用 nodemon
監聽build
目錄變化,每次目錄變化就重新執行主程式,同時使用wait-on
等渲染程序執行完後再執行主程序。使用 npm-run-all --parallel
讓所有指令碼同步觸發,只用開一個埠就可啟動程式。
然後執行yarn start
即可啟動程式。
應用打包配置
市面上主要的打包外掛有electron-builder
和electron-packager
,這裡我使用的electron-builder
。
yarn add electron-builder -D
複製程式碼
1.修改配置檔案
因為打包後與執行時的路徑有差別,所以這裡要修改一下之前的程式碼:
引入electron-is-dev
判斷程式執行環境:
yarn add electron-is-dev -D
複製程式碼
// main/index.ts
// 該檔案的修改主要是關於引入了 electron-is-dev 後的操作
import { app,BrowserWindow } from 'electron'
import path from 'path'
import isDev from 'electron-is-dev'
let win: BrowserWindow | null = null
function createWindow() {
// ...
// 根據執行環境選擇
const urlLocation = isDev
? 'http://localhost:3000'
: `file://${path.join(__dirname,'./index.html')}` // 這裡的路徑是相對於打包後的檔案路徑
win.loadURL(urlLocation)
// ...
}
// ...
複製程式碼
然後增加一項webpack
配置:
// config/webpackConfig.ts
import { Configuration } from 'webpack'
import path from 'path'
class WebpackConfig implements Configuration {
// ...
node: Configuration['node'] = {
// 預設值是 'mock',會將其轉化為'/',我們這裡並不是服務端,應該設定為 false ,表示輸出檔案的目錄名,在打包程式碼裡面也要一直將其當作打包後的檔案路徑使用
__dirname: false, __filename: false
}
// ...
}
export default WebpackConfig
複製程式碼
再修改一下package.json
中的配置項:
{
"homepage": "./",
"build": {
"appId": "testId", "productName": "testName", "files": [
"build/**/*"
], "extraMetadata": {
"main": "./build/main.js"
}, "directories": {
"buildResources": "assets"
}
},}
複製程式碼
「配置說明:」
修改
homepage
時因為 cra 打包時預設會以該項的路徑為基礎進行打包,因為我們打包後不是在伺服器使用,所以應該改為相對路徑。至於
build
選項則是electron builder
的打包配置,具體細節要根據實際專案進行配置,該專案要能正常打包成桌面應用其實只需要上面的:
{
"files": [
"build/**/*"
], "extraMetadata": {
"main": "./build/main.js"
}
}
複製程式碼
就可以成功進行應用打包了。其餘的配置項請自行參考 electron-builder 官網。
2.配置打包指令碼
生產模式指令碼如下:
{
"build:render": "react-scripts build", "build:main": "cross-env NODE_ENV=productment ts-node --project ./config/tsconfig.json ./config/start.ts", "build": "npm-run-all build:*", "prepack": "npm run build", "dist": "electron-builder", "pack": "electron-builder --dir", "package-all": "npm run build && electron-builder build -mwl", "package-mac": "npm run build && electron-builder build --mac", "package-linux": "npm run build && electron-builder build --linux", "package-win": "npm run build && electron-builder build --win --x64"
}
複製程式碼
然後執行yarn run pack
就可在當前目錄生成符合使用者對應作業系統的應用了。
3.注意事項
由於electron-builder
會將dependencies
的依賴都打包進去,所以為了減小打包體積,儘量將依賴都放到devDependencies
中
總結
於此,就搭建了一個簡易的集成了 react 與 typescript 的開發 electron 開發環境。
該模版的全部程式碼已釋出到 github。