Angular 12 で Electron によるデスクトップアプリ化

すでに Angular プロジェクトが作成されているものとする

## 参考サイト

https://github.com/bampakoa/angular-electron-sample

## 環境

Angular バージョン

```
ng version

     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/
    

Angular CLI: 12.2.10
Node: 14.15.1
Package Manager: npm 8.1.0
OS: darwin x64

Angular: 12.2.10
... animations, cdk, cli, common, compiler, compiler-cli, core
... forms, localize, material, platform-browser
... platform-browser-dynamic, router

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1200.5
@angular-devkit/build-angular   12.2.10
@angular-devkit/core            12.0.5
@angular-devkit/schematics      12.2.10
@angular/fire                   6.1.5
@schematics/angular             12.2.10
rxjs                            6.6.7
typescript                      4.3.5
```

## モジュールの追加

`@types/node` を新しいバージョンに上げておく

```
    "@types/node": "^14.15.5",
```

Angular12 で electron を実装するのに必要なモジュールを追加する

```json:package.json
"dependencies" or "devDependencies": {
...
    "ngx-electron": "^2.2.0",
    "ts-loader": "^9.2.5",
    "concurrently": "^6.2.1",
    "electron": "^13.2.0",
    "electron-log": "4.4.1",
    "electron-packager": "^15.3.0",
    "webpack-cli": "^4.8.0",
    "webpack-node-externals": "^3.0.0"
}
```

## webpack.**.config.js の追加

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142847/257da53b-f791-f4d5-da7e-e863a7b553c9.png)

```javascript:webpack.electron.config.js
const path = require('path');
const { ProgressPlugin } = require('webpack');

const src = path.join(process.cwd(), 'src', 'electron');

module.exports = {
  resolve: {
    extensions: [
      '.ts',
      '.js'
    ]
  },
  entry: {
    main: path.join(src, 'main.ts')
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        loader: 'ts-loader',
        options: {
          configFile: path.join(src, 'tsconfig.json')
        }
      }
    ]
  },
  plugins: [
    new ProgressPlugin()
  ],
  // indicates that transpilation code is to be run in the main process
  target: 'electron-main'
};
```

```javascript:webpack.electron.dev.config.js
const path = require('path');
const baseConfig = require('./webpack.electron.config');

module.exports = {
  ...baseConfig,
  mode: 'development',
  devtool: 'source-map',
  output: {
    path: path.join(process.cwd(), 'dist'),
    // avoid conflicts with the `main.js` file generated from the Angular CLI
    filename: 'shell.js'
  }
};
```

```javascript:webpack.electron.prod.config.js
const path = require('path');
const nodeExternals = require('webpack-node-externals');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const baseConfig = require('./webpack.electron.config');

module.exports = {
  ...baseConfig,
  mode: 'production',
  output: {
    path: path.join(process.cwd(), 'dist'),
    filename: 'main.js'
  },
  plugins: [
    // the electron-packager requires a valid `package.json` file along with the Electron code
    new CopyWebpackPlugin({
      patterns: [
        {
          context: path.join(process.cwd(), 'src', 'electron'),
          from: 'package.json'
        }
      ]
    })
  ],
  // Indicate that we do not want to bundle modules from 3rd party libraries automatically, as part of ES6 imports.
  // Instead we must define them in the `dependencies` property of the `package.json` file and will be installed
  // using the `package` npm script
  externals: [nodeExternals()]
};
```

## npm run の スクリプトを追加

```json:package.json
  "scripts": {
...
    "build:dev": "concurrently \"ng build --delete-output-path=false --watch\" \"webpack --config webpack.electron.dev.config.js --watch\"",
    "start:dev": "electron dist/shell.js",
    "build:prod": "ng build --optimization --outputHashing=all --sourceMap=false --namedChunks=false --extract-licenses --vendorChunk=false --build-optimizer && webpack --config webpack.electron.prod.config.js",
    "start:prod": "electron dist/main.js"
  }
```

テストする時は
For the desktop version run:

```
npm run build:dev
npm run start:dev
```

## electron 起動用の main.ts を用意する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142847/978e5cd7-52b7-f579-3078-1a3e85ae377e.png)

```typescript:src/electron/main.ts
import { app, BrowserWindow, ipcMain, dialog } from 'electron';
import * as fs from 'fs';
import log from 'electron-log';

log.info(`${app.name} ${app.getVersion()}`);

function createWindow () {
  const mainWindow = new BrowserWindow({
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false
    }
  });
  mainWindow.maximize();
  mainWindow.webContents.openDevTools();
  mainWindow.loadFile('index.html');
}

app.whenReady().then(() => {
  createWindow();
});

// Angular -> Electron
ipcMain.on('selectPirate', async (event: Electron.IpcMainEvent, name: string) => {
    await dialog.showMessageBox({ message: 'You selected: ' + name });
    event.returnValue = true;
});
```


```json:src/electron/package.json
{
    "name": "angular-electron-sample",
    "description": "Angular Electron Sample",
    "version": "0.0.0",
    "author": {
      "name": "Aristeidis Bampakos"
    },
    "main": "main.js",
    "dependencies": {
      "electron-log": "4.3.1"
    }
}
```


```json:src/electron/tsconfig.json
{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "importHelpers": false
  },
  "include": [
    "**/*.ts"
  ]
}
```

# テスト

### ビルドしてみる

```
build:dev
```
![Jan-30-2022 09-45-18.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142847/ef9af33e-8bef-09ca-9c77-8c316495f16a.gif)

最後フリーズした状態になるけど気にせず ctrl+c で強制終了する

### 起動してみる

```
start:dev
```

# Electron用の動作 を Angule アプリに追加する

## app.module に追加

```typescript:src/app/app.module.ts
import { ElectronService, NgxElectronModule } from "ngx-electron";

...

@NgModule({
  imports: [

    NgxElectronModule
  ],
  providers: [

    ElectronService
  ]
```

## electron を使うコンポーネントに追加

```typescript:**.component.ts
import { ElectronService } from 'ngx-electron';

  constructor(
    public electronService: ElectronService
   ) {}

  // electron を使う関数
  public use_electron(): void{
    if(this.electronService.isElectronApp) {
      this.electronService.ipcRenderer.sendSync('selectPirate', 'hogehoge');
    }
  }
```


# おわりに

Angular 12 では なかなか情報がみつからなかったので書きました