使用 typescript 搭建一个基于 cra 的 electron 模板

时间:2020-7-2 作者:admin

前言

最近要写一个 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-builderelectron-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

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。