Cordova+Angularデバッグ環境構築

# はじめに

Cordovaを利用したAndroidアプリを開発するとき、デバッグに苦労したので備忘録として書き起こします。
まずは、自分用に作成しているので、説明不足等があるかと思います。
都度指摘していただけるとありがたいです。

# 更新履歴

* 2020-03-26 - Angular CLI と cordova のプロジェクトディレクトリを同一にしました。
* 2020-03-26 - Angular9で確認
* 2020-03-26 - Visual Studio Codeでのデバッグがうまくいかない場合の対策を追記
* 2020-03-26 - cordovaとAngular CLIプロジェクトを同一ディレクトリに統合
* 2020-03-27 - Angular CLIのサブプロジェクトに対応
* 2020-03-28 - おまけ: Angular Material
* 2020-03-28 - おまけ: Material Design Icons
* 2020-03-28 - おまけ: Roboto Font
* 2021-11-05 - npxを利用

# 動機

動機はですね、手短に3行で書くとこんな感じです。

**「Cordova+Angular+Androidでアプリ開発？ 不具合見つかったときの調査や切り分けってどうやるん？」
「テストやリリース後の不具合調査でデバッガ使えないのは恐怖以外ないじゃろうが！！」
「ワシは、実機でブレークポイント置いてデバッグしたいんじゃ～っ！」**

というわけで、色々と調べてやってみると出来ましたので、まとめてみました。

尚、「ionic使えばええやん？」というご意見があります。ごもっともです。
ですので、各開発プロジェクトの状況によって判断ください。
私は検証目的で、最新のAngular+Cordovaを使う必要があるので、使っていません。
また、もし、ionic上の問題にぶつかったときの回避策・選択肢として、本ドキュメントが役立ててもらえることを期待してたりします。

# 前提

以下をインストール済みであるとして、以降の説明をします。

* Windows10 Pro 64bit
* [Node.js 12.16.1](https://nodejs.org)
* [Angular CLI 9.1.8\0](https://angular.io/cli)
* [Cordova 9 / cordova-android 8.0](https://cordova.apache.org/)
* [Android Studio/Android SDK](https://developer.android.com/studio)
* [VSCode 1.43.2](https://code.visualstudio.com/updates/v1_33)
* [VSCode - Cordova Tools](https://marketplace.visualstudio.com/items?itemName=vsmobile.cordova-tools)

# Cordova+Angularアプリ構成

ググると、CordovaのGUI部分をAngular/Angular CLIで作成するときの構成は、いろいろとあります。
大きくは以下のような感じ？でしょうか？

* Cordovaプロジェクトの上にAngular CLIプロジェクトを上書きする。
* Cordovaプロジェクトの直下にsrcディレクトリを作成して、そこにAngular CLIプロジェクトを作成する。
* CordovaプロジェクトとAngular CLIプロジェクトを独立させる。

詳しくは説明しませんが、定番の定石は無さそうです。
うーん、困った…。

以前は、今回は同一ディレクトリ階層にAngular CLIとCorodova CLIのプロジェクトルートを並べて配置していました。
この構成だと、node_modulesを共有しないので安全かな？と思っていましたが、デバッグ環境の構築がメンドクサイので、Angular CLIプロジェクトディレクトリとCordovaのプロジェクトディレクトリ(cordova-angular)を同一にしました。

# 作成手順

VSCodeでブレイクポイントを設定して、デバッグ実行時に処理がストップさせるためのサンプルプロジェクトの作成手順を記載します。

## プロジェクト作成

```text
# cordovaプロジェクト生成
> npx cordova create cordova-angular net.kyosho.cordova.angular cordova-angular

# package.jsonバックアップ
> rename cordova-angular\package.json package.json.back

# Angularプロジェクト生成
> npx ng new cordova-angular --create-application=false --routing=false --style=scss

# Angularサブプロジェクト生成
> cd cordova-angular
> npx ng g application app --routing=true --style=scss

# androidプラットフォーム追加
> npx cordova platform add android
```

上記までの作業で、ディレクトリは以下のようになっているはずです。

```text
\---cordova-angular
    |   angular.json // Angular CLIの構成ファイル
    |   tsconfig.json
    |   
    +---hooks
    +---node_modules
    +---platforms
    +---plugins
    +---projects
    |   \---app // サブプロジェクトディレクトリ
    |       +---e2e
    |       |   \---src
    |       \---src
    |           |   index.html // ルートHTML
    |           |
    |           +---app
    |           |       app.component.html // 画面部分のHTML
    |           |       app.component.ts // 画面部分に対するバックグラウンド処理を記述するTypescript
    |           |
    |           +---assets
    |           \---environments
    \---www
```

## ファイル修正

`ng build`の出力先がwwwディレクトリとなるように、`angular.json` の"outputPath"を以下のように変更します。

```json:angular.json
{
  ...
  "projects": {
    "angular-app": {
      "architect": {
        "build": {
        ...
          "options": {
            "outputPath": "www",
            ...
          },
        ...
    }},
    ...
}
```

tsconfig.tsのtargetをES5にします。

```json:tsconfig.json
{
  "compilerOptions": {
    ...
    "target": "ES5",
    ...
  }
}
```

Cordovaのアプリケーション開始イベント`deviceready`発生後に、Angularを起動するように`projects/app/src/main.ts`を修正します。
この設定がないと、cordova pluginの機能をうまく使えなかったりします。

```typescript:projects/app/src/main.ts
...
const onDeviceReady = () => {
  platformBrowserDynamic().bootstrapModule(AppModule)
    .catch(err => console.error(err));
};

document.addEventListener('deviceready', onDeviceReady, false);
```

`projects/app/src/index.html` に`<meta name="viewport" ...>`, `<meta http-equiv="Content-Security-Plicy" ...>`, `<script type="text/javascript" src="cordova.js"></script>`を追加・修正します。

```html:projects/app/src/index.html
<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>App</title>
  <base href="/">

  <!-- meta name="viewport" を追加します。 -->
  <meta name="viewport" content="initial-scale=1, width=device-width, viewport-fit=cover">

  <!-- meta http-equiv="Content-Security-Policy" を修正します。 -->
  <meta http-equiv="Content-Security-Policy"
    content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *; img-src 'self' data: content:;">

  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>

<body>
  <app-root></app-root>

  <!-- cordova.jsを参照します。 -->
  <script type="text/javascript" src="cordova.js"></script>
</body>

</html>
```

デバッガでブレイクポイントを設定するために、
`projects/app/src/app/app.component.html`にボタンを追加します。
ボタンを押すと、画面に現在時刻を表示します。

```html:projects/app/src/app/app.component.html
<button (click)="clicked($event)">button</button>
<br />
{{message}}
```

ボタンクリック時のイベントハンドラ(onClick()メソッド)を、
`projects/app/src/app/app.component.ts`に追加します。

```typescript:projects/app/src/app/app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  message = 'メッセージ';

  // HTML上のボタンをクリックすると呼ばれるメソッドです。
  clicked(event: Event) {
    this.message = (new Date()).toString();
    console.log(this.message);
  }
}
```

# ビルド

上記の設定が済んだら、ビルドします。

```text
# Webアプリをビルドします。
# ビルド先は、../cordova-angular/www となります。
# "--base-href ./"は指定しないと表示されないので必ず付けてください。
> npx ng build --base-href=./
```

# 動作確認

```text
# Android エミュレータ上で起動します。
> npx cordova emulate android
```

上記を実行すると、以下のAndroidアプリが起動します。
画面に作成したWebアプリが表示されていれば成功です。
ボタン"button"をクリック(タップ)すると現在時刻が表示されます。

![004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179413/7ccecc8f-3835-8ec3-0c1a-66b0d9107551.png)

# VSCodeでデバッグ

作成したサンプルをデバッグします。
以下コマンドでVSCodeを起動してください。
起動時にcordova-projectディレクトリを指定することが大事です。

```text:command.txt
# VSCodeでcordova-angularディレクトリを開く
> code %ROOT%\cordova-angular
```

[Cordova Tools](https://marketplace.visualstudio.com/items?itemName=vsmobile.cordova-tools) をインストールされていなければ、このタイミングでインストールしておいてください。
VSCodeの画面左側にある"虫"ボタンをクリックすると以下画面が表示されます。
歯車ボタンにオレンジのぽっちんが出てると思うので、クリックするとデバッグ環境を選択できます。

![005.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179413/fd5d645f-4a93-5f25-7398-c656c2fa98c6.png)

いろいろと表示できますが、"Cordova"を選択してください。

![006.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179413/0233d9d2-b5ab-b8db-37bd-04d1b1fd530c.png)

すると`launch.json`が作成され、"Run Android on emulator"が追加されるはずです。
そのままだとブレイクポイントが無効となってしまうので、`launch.json`に`sourceMapPathOverrides`を追加してください。

```json:.vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        ...
        {
            "name": "Run Android on emulator",
            "type": "cordova",
            "request": "launch",
            "platform": "android",
            "target": "emulator",
            "port": 9222,
            "sourceMaps": true,
            "cwd": "${workspaceFolder}",
            "ionicLiveReload": false,
            "sourceMapPathOverrides": {
                "webpack:///./*": "${cwd}/projects/app/*",
                "webpack:///src/*": "${cwd}/projects/app/src/*",
                "webpack:///*": "${cwd}/projects/app/*",
                "webpack:///./~/*": "${cwd}/node_modules/*",
                "./*": "${cwd}/projects/app/*"
            }
        },
        ...
    ]
}
```

以下のようにDEBUG CONSOLEを表示した状態にしてください。ショートカットは`Ctrl+Shift+Y`です。
DEBUG:"Run Android on emulator"を選択できるようになっているので、選択してデバッグ再生ボタン(緑の再生ボタン)をクリックしてください。
するとコマンドプロンプトで`cordova emulate android`を実行したときと同様にAndroidアプリが起動します。
起動したAndroidアプリ上でボタン"button"をタップすると、画面の`メッセージ`部分に`現在時刻`が表示されます。

それではブレイクポイント(画面の赤いぽっちん)を配置してみましょう。
以下スクリーンショットと同様の箇所をクリックすると配置されます。

![007.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179413/b1c25c9b-da47-bf38-d7d6-a8cb19263abb.png)

ブレイクポイントを配置してから、再度ボタン"button"をタップしてください。
以下のように処理を一時停止できます！

![008.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179413/93285f5d-bfe3-8864-a897-cfeba4416623.png)

これで実機で不具合が起こっても、調査できそうですね。

# おまけ

ionicには様々なUIを用意してくれています。
完全な代替とは言えませんが、Angular Materialを導入する手順を書きます。
足りないGUI等があれば、ionicで作るもヨシ、自分でGUIを作るのもヨシ、他OSSを利用するのもヨシ！かと考えます。

## Angular Material

以下を実行するだけです。
いくつか確認されますが、そのまま[Enter]でOKです。

```text
> npx ng add @angular/material
? Choose a prebuilt theme name, or "custom" for a custom theme: Indigo/Pink        [ Preview: https://material.angular.io?theme=indigo-pink ]
? Set up global Angular Material typography styles? No
? Set up browser animations for Angular Material? Yes
UPDATE package.json (1812 bytes)
√ Packages installed successfully.
UPDATE angular.json (4279 bytes)
UPDATE projects/camera-app/src/styles.scss (181 bytes)
```

Angular Material をインストールすると以下のタグが追加されます。

```html:projects/app/src/index.html
<!doctype html>
<html lang="en">

<head>
  ...
  <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap" rel="stylesheet">
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  ...
</head>
...
</html>
```

これはGoogleからアイコンフォントとフォントを取得するので、オフライン時にエラーが発生することがあります。
また、折角ですから、全てCordovaのアプリ内で完結する必要があります。

## アイコンとフォントのローカルインストール

アイコンをローカルにインストールして使えるようにします。
まずは、以下をインストールします。

```text
> npm install --save material-design-icons
> npm install --save typeface-roboto
```

`projects/app/src/index.html`のタグをコメントアウトするか削除します。

```html:projects/app/src/index.html
<!doctype html>
<html lang="en">

<head>
  ...
  <!-- <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap" rel="stylesheet"> -->
  <!-- <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> -->
  ...
</head>
...
</html>
```

代わりに、`projects/app/src/style.scss`に以下を追加します。

```css:projects/app/src/style.scss
...
@import 'material-design-icons/iconfont/material-icons.css';
@import 'typeface-roboto/index.css';
...
```

これで大丈夫！(なはず…)