💡

Electron + React の TypeScript プロジェクトを開始する

2021/09/22に公開約9,700字

React + Electron のプロジェクトを TypeScript で始める手順を紹介します。
ターミナルで以下のコマンドを実行します。

$ npx create-react-app . --template typescript
$ yarn add @types/electron-devtools-installer electron-devtools-installer electron-is-dev electron-reload
$ yarn add -D concurrently electron electron-builder wait-on cross-env
$ mkdir electron && touch electron/{main.ts,preload.ts,tsconfig.json}

electron/tsconfig.json を編集します。

electron/tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "sourceMap": true,
    "strict": true,
    "outDir": "../build", // Output transpiled files to build/electron/
    "rootDir": "../",
    "noEmitOnError": true,
    "typeRoots": [
      "node_modules/@types"
    ]
  }
}

electron/main.ts を編集します。

electron/main.ts
import { app, BrowserWindow } from 'electron';
import * as path from 'path';
import * as isDev from 'electron-is-dev';
import installExtension, { REACT_DEVELOPER_TOOLS } from "electron-devtools-installer";

let win: BrowserWindow | null = null;

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

  if (isDev) {
    win.loadURL('http://localhost:3000/index.html');
  } else {
    // 'build/index.html'
    win.loadURL(`file://${__dirname}/../index.html`);
  }

  win.on('closed', () => win = null);

  // Hot Reloading
  if (isDev) {
    // 'node_modules/.bin/electronPath'
    require('electron-reload')(__dirname, {
      electron: path.join(__dirname, '..', '..', 'node_modules', '.bin', 'electron'),
      forceHardReset: true,
      hardResetMethod: 'exit'
    });
  }

  // DevTools
  installExtension(REACT_DEVELOPER_TOOLS)
    .then((name) => console.log(`Added Extension:  ${name}`))
    .catch((err) => console.log('An error occurred: ', err));

  if (isDev) {
    win.webContents.openDevTools();
  }
}

app.on('ready', createWindow);

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

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

package.json に以下を追記します。

package.json
+  "homepage": ".",
+  "main": "build/electron/main.js",
+  "build": {
+    "extends": null,
+    "files": [
+      "build/**/*"
+    ],
+    "directories": {
+      "buildResources": "assets"
+    }
+  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
+    "postinstall": "electron-builder install-app-deps",
+    "electron:dev": "concurrently \"cross-env BROWSER=none yarn start\" \"wait-on http://localhost:3000 && tsc -p electron -w\" \"wait-on http://localhost:3000 && tsc -p electron && electron .\"",
+    "electron:build": "yarn build && tsc -p electron && electron-builder"
  },

以下のコマンドを実行すると、 Electron が起動します。

$ yarn electron:dev

ESLint の設定

デフォルトの設定を一部削除します。

package.json
    "electron:build": "yarn build && tsc -p electron && electron-builder"
  },
-  "eslintConfig": {
-    "extends": [
-      "react-app",
-      "react-app/jest"
-    ]
-  },
  "browserslist": {
    "production": [
$ yarn add -D eslint
$ npx eslint --init
✔ How would you like to use ESLint? · style
✔ What type of modules does your project use? · esm
✔ Which framework does your project use? · react
✔ Does your project use TypeScript? · No / Yes
✔ Where does your code run? · browser
✔ How would you like to define a style for your project? · guide
✔ Which style guide do you want to follow? · airbnb
✔ What format do you want your config file to be in? · JSON
✔ Would you like to install them now with npm? · Yes

Prettier の設定

$ yarn add prettier --dev --exact
$ touch {.prettierrc.json,.prettierignore}
.prettierrc.json
{
  "trailingComma": "es5",
  "tabWidth": 2,
  "semi": false,
  "singleQuote": true
}
.prettierignore
node_modules
# Ignore artifacts:
build
coverage

ESLint と Prettier を併用する設定

$ yarn add eslint-config-prettier --dev
$ yarn add eslint-plugin-prettier --dev

.eslintrc.json を編集する。

.eslintrc.json
    "extends": [
        "plugin:react/recommended",
        "airbnb",
+        "prettier",
+        "plugin:prettier/recommended"
    ],

ESLint の警告への対処

yarn electron:dev を実行すると、 Failed to compile と様々な警告が表示されます。
それぞれの対処法を見ていきます。

EsLint: Parsing error: Invalid value for lib provided: es2021

.eslintrc.jsonparserOptions を修正する。

.eslintrc.json
"parserOptions": {
  ...,
  "ecmaVersion": 2020
},

EsLint: Parsing error: Invalid value for lib provided: es2021

JSX not allowed in files with extension '.tsx'eslint(react/jsx-filename-extension)

.eslintrc.jsonrules を追加する。

.eslintrc.json
rules:  {
  'react/jsx-filename-extension': [2, { 'extensions': ['.js', '.jsx', '.ts', '.tsx'] }],
},

JSX not allowed in files with extension '.tsx'eslint(react/jsx-filename-extension)

src/index.tsx Line 4:17: Unable to resolve path to module './App' import/no-unresolved

$ yarn add eslint-config-airbnb-typescript
.eslintrc.json
extends: [
  'airbnb',
+ 'airbnb-typescript'
]

eslint-config-airbnb-typescript

Error generating parserServices for eslint plugin @typescript-eslint

Error while loading rule '@typescript-eslint/dot-notation'のようなエラーが出た場合は、以下の設定を追加します。

.eslintrc.json
"parserOptions": {
  "ecmaVersion": 2020,
+  "project": ["tsconfig.json"] ,
  "sourceType": "module"
},

Error generating parserServices for eslint plugin @typescript-eslint

最終的な .eslintrc.json

.eslintrc.json
{
  "env": {
    "browser": true,
    "es2021": true
  },
  "extends": [
    "plugin:react/recommended",
    "airbnb",
    "airbnb-typescript",
    "prettier",
    "plugin:prettier/recommended"
  ],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "ecmaVersion": 2020,
    "project": ["tsconfig.json"] ,
    "sourceType": "module"
  },
  "plugins": ["react", "@typescript-eslint"],
  "rules": {
    "react/jsx-filename-extension": [
      2,
      { "extensions": [".js", ".jsx", ".ts", ".tsx"] }
    ]
  }
}
package.json
{
  "name": "sample-project",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.11.4",
    "@testing-library/react": "^11.1.0",
    "@testing-library/user-event": "^12.1.10",
    "@types/electron-devtools-installer": "^2.2.0",
    "@types/jest": "^26.0.15",
    "@types/node": "^12.0.0",
    "@types/react": "^17.0.0",
    "@types/react-dom": "^17.0.0",
    "electron-devtools-installer": "^3.2.0",
    "electron-reload": "^2.0.0-alpha.1",
    "eslint-config-airbnb": "^18.2.1",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-scripts": "4.0.3",
    "typescript": "^4.1.2",
    "web-vitals": "^1.0.1"
  },
  "homepage": ".",
  "main": "build/electron/main.js",
  "build": {
    "extends": null,
    "files": [
      "build/**/*"
    ],
    "directories": {
      "buildResources": "assets"
    }
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "postinstall": "electron-builder install-app-deps",
    "electron:dev": "concurrently \"cross-env BROWSER=none yarn start\" \"wait-on http://localhost:3000 && tsc -p electron -w\" \"wait-on http://localhost:3000 && tsc -p electron && electron .\"",
    "electron:build": "yarn build && tsc -p electron && electron-builder"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "@typescript-eslint/eslint-plugin": "4.0.1",
    "@typescript-eslint/parser": "4.0.1",
    "concurrently": "^6.2.1",
    "cross-env": "^7.0.3",
    "electron": "^15.0.0",
    "electron-builder": "^22.11.7",
    "electron-is-dev": "^2.0.0",
    "eslint": "^7.32.0",
    "eslint-config-airbnb-typescript": "^14.0.0",
    "eslint-config-prettier": "^8.3.0",
    "eslint-plugin-import": "^2.24.2",
    "eslint-plugin-jsx-a11y": "^6.4.1",
    "eslint-plugin-prettier": "^4.0.0",
    "eslint-plugin-react": "^7.26.0",
    "eslint-plugin-react-hooks": "^4.2.0",
    "prettier": "2.4.1",
    "wait-on": "^6.0.0"
  }
}

prettier でフォーマットする

$ npx prettier --write .

参考:

GitHubで編集を提案

Discussion

ログインするとコメントできます