Vue3 × ネイティブアプリ開発ーNativeScript、Capacitorについて調べてみたー

# Vueでもネイティブアプリ開発がしたい！！！

近年のWebサイト・アプリはスマホユーザの割合が上昇し続けており、開発者・デザイナとしてもレスポンシブ対応やネイティブユーザを意識したUI・UX、機能提供の重要性が増しています。

https://ecclab.empowershop.co.jp/archives/92824

その中で一度は挙がるであろう要望、
### **「Web経由じゃなくてネイティブアプリを提供したい」**

これに対応する場合、プロダクトのターゲットや性質、チームリソースによって様々なアプローチが考えられます。

1. iOS/Androidをそれぞれネイティブ対応する
2. React NativeやFlutterのような両OSに対応したフレームワークを利用する
3. PWAを構築し、ユーザへ利用を促す

それぞれ一長一短ではありますが、すでにWebアプリを開発・運用している企業で、特にネイティブ特有のAPIを使いたい訳ではない & マーケティング上ストアへのリリースに意味がある(と言われた)場合は **「2」** の選択肢が多いのではないでしょうか。

Reactであれば先述した「React Native」が有名ですが、Vueでは未だ「これ！」という方法があまりないように思えます。

### 「VueでもネイティブなiOS・Androidのアプリが作りたい〜〜〜！」
### 「けれどSwiftやKotlinで書き直さず、極力ソースコードを一元管理したい...」

...今回はそのような問題をVueアプリで解決する方法がないか調べてみました。

# 前提
#### ネイティブの最新機能やウィジェットを使わなくて良いアプリを移行する
→ カメラやバックグラウンドタスク、位置情報、Face ID、決済など概ねの機能は利用できるようになっていますが、一部機能は利用できないことがあります。
最近だとiOSの ```ライブアクティビティ``` など、iOSの最新版から利用可能！のような機能はフレームワークの対応次第になるためすぐには実装できない場合があります。

■ライブアクティビティ(Dynamic Island)
https://support.apple.com/ja-jp/guide/iphone/iph28f50d10d/ios
https://developer.apple.com/documentation/ActivityKit/


#### ネイティブアプリ開発に必要なセットアップ済んでいる
→iOSアプリならMacにXCode、AndroidアプリならAndroid Studioがインストールされていることなど...
詳細な方法については様々な記事があるのでここでは割愛します。

#### Vue3が動作する
→NodeのバージョンアップやVue3アプリのセットアップ方法は割愛します。

## ネイティブ化の案
過去利用されていたライブラリも含め、いくつか案を挙げてみます。

---
* ~~Vue Native【サポート終了】~~
→React Nativeのラッパーライブラリであり、React NativeでできることをVueでもできるようになっていた。現在はメンテナンスモードとなりVue3では利用不可

https://vue-native.io/

* Native Script【Vue3 β対応】
→VueだけでなくTypeScriptやAngular、ReactでネイティブUIコンポーネントが生成できるライブラリ。
DOMが利用できないためVueのUIライブラリが使えなかったり、ルーティングがVueRouter非対応だったりするため注意が必要(Piniaは対応)
非対応部の開発を許容できるリソースがあるなら良いかもしれない。
##### ■Vue3対応版NSドキュメント

https://nativescript-vue-docs-v3.pages.dev/

* Capacitor【Vue3対応】
→WebViewラッピングを前提としたマルチプラットフォーム開発ライブラリ。
様々なJSフレームワークでネイティブ開発が可能で、Vue3にも対応している。
WebViewなので主要なライブラリも大抵動作する(一部制限あり)

https://capacitorjs.com/solution/vue

---

今回はNativeScriptとCapacitorを試してみました。


# NativeScriptのβ版を試してみる

上述したNativeScriptを試してみます。

https://nativescript.org/

https://nativescript-vue-docs-v3.pages.dev/


NativeScriptはベータではありますが、Vue3にも対応しており、糖衣構文で書くことができます。

**ex) ListViewの構築**
```vue
<script setup lang="ts">
...
</script>
<template>
    <ListView :items="listOfItems" @itemTap="onItemTap">
      <template #default="{ item, index, even, odd } : { item: string, index: number, even: boolean, odd: boolean }">
        <!-- Shows the list item label in the default color and style. -->
        <StackLayout>
          <Label :text="item" />
          <Label :text="`Item index ${index}`" />
          <Label :text="`Is event ${even}`" />
          <Label :text="`Is odd ${odd}`" />
        </StackLayout>
      </template>
    </ListView>
</template>
```

https://nativescript-vue-docs-v3.pages.dev/docs/elements/components/list-view.html


## 動作状況は・・・？

### ディレクティブ
基本的なAPIには対応しており、defineModelも動作していることを確認。
ただしv-if、v-showなど所々で制御が効かなくなる場面に遭遇（devモード特有？）

### 周辺ライブラリの互換性
Vue周りのライブラリ

| ライブラリ | 動作 | 代替案 |
| ---- | ---- | ---- | 
| Pinia | ◯ |  |
| Vue Router4 | ❌ | 手動でルーティング構築 |
| VueUse | △ |  |
| Vuetify | ❌ | ネイティブUI or NSプラグイン利用 |
| Quasar | ❌ | ネイティブUI or NSプラグイン利用 |

各ライブラリの対応状況や代替案は以下より確認できます。

https://nativescript-vue-docs-v3.pages.dev/

※Piniaの利用方法やルーティングの代替案等ドキュメントのリンクが直でアクセスすると404になってしまうので遷移順を書いておきます。

#### Pinia利用方法
「Get Started」 > 左側メニュー「Vue Plugins」 > 「Pinia」

#### ルーティング代替案
「Get Started」 > 左側メニュー「Manual Routing」


### 既存のコンポーネントを再利用できる？
残念ながらNativeScriptではWeb用に開発したSFCをネイティブ用に利用することはできません。
というのも```div```や```p```といったWebのDOMがネイティブでは機能しないからです。

#### ざっくり置き換え表
| Web DOM | ネイティブ |
| ---- | ----- |
| input[type='date'] | DatePicker |
| input[type='text'] | TextField |
| p,h* | Label |
| img | Image |


ただしPure DOMであればWebViewラッパーを利用することで制限はあるものの、描画することが可能であることを確認しました。

https://nativescript-vue.org/ja/docs/elements/components/web-view/

UIライブラリ対応だとかなり複雑化しそうですが、ネイティブUIコンポーネントを生成できるメリットが大きいのでpureDOM to native パーサーを作ってみるのもありだと感じました。

## Capacitorを使ってみる

Capacitorの利点は既存のVueアプリに設定とライブラリを追加することでネイティブアプリケーションリリースができることです。
1年半利用されたzuckeyさんの記事でも、拡張の容易さとスピード感持って開発を維持できたことを利点としています。

>Webに機能を追加するだけで iOS / Android にも対応できるのは純粋に短縮になります。また、機能や画面構成などが大きく変わらないような修正であれば審査無しでリリースすることができたのでこの点もスピード感に寄与しました。APIの互換性を壊したアップデートについても少し気をつけるだけで済むのは大きなアドバンテージです。

https://studist.tech/look-back-one-and-half-year-with-capacitor-3c9e23a4a8d3

### セットアップ
以下手順で組み込んでいきます。

https://capacitorjs.com/solution/vue

#### 0→1 ざっくり手順

1.Vueアプリ作成

```
npm create vue@latest
```

2.Capatitorのセットアップ
```
yarn add @capacitor/core @capacitor/cli
npx cap init
```

3.iOS/Android開発用のライブラリ追加
```
yarn add @capacitor/ios @capacitor/android
npx cap add android

npx cap add ios
```

4.Capacitorの位置情報ライブラリをインストール
```
yarn add @capacitor/geolocation
```

5.Vueの初期アプリに位置情報機能を追加してみる
```NativeTest.vue
<template>
    <div>
        <h1>Geolocation</h1>
        <p>Your location is:</p>
        <p>Latitude: {{ loc.lat }}</p>
        <p>Longitude: {{ loc.long }}</p>

        <button @click="getCurrentPosition">
            Get Current Location
        </button>
    </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { Geolocation } from '@capacitor/geolocation';
const loc = ref<{
    lat: null | number;
    long: null | number;
}>({
    lat: null,
    long: null,
});

const getCurrentPosition = async () => {
    const pos = await Geolocation.getCurrentPosition();
    loc.value = {
        lat: pos.coords.latitude,
        long: pos.coords.longitude,
    };
};
</script>
```

結果、非常に容易に位置情報APIを組み込むことができました。
![スクリーンショット 2024-12-08 14.40.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2840428/ed251767-3ccc-4f9c-1d7b-514f8f90a446.png)

他にもネイティブアプリでよく見かける「バックグラウンドタスク」にも対応しているようです。

https://capacitorjs.com/docs/apis/background-runner

# Web→ネイティブにおける注意点

## 必要な権限の設定

カメラや位置情報等、ネイティブ機能を利用してアプリをリリースする場合、アプリ説明文への記載やマニュフェストファイルの調整が必要になります。

### iOS: ```Info.plist```
>iOS permissions do not need to be specified explicitly like they are in Android. However, iOS requires "Usage Descriptions" to be defined in Info.plist. These settings are human-readable descriptions that will be presented to the end user when permission is requested for a particular device API.

>Consult the Cocoa Keys list for keys containing ```UsageDescription``` to see the various usage description settings that may be required for your app.

>For more information, Apple has provided a guide to Resolving the Privacy-Sensitive Data App Rejection which contains more information on APIs that require usage descriptions.

>iOSのパーミッションは、Androidのように明示的に指定する必要はない。しかし、iOSではInfo.plistで 「Usage Descriptions」を定義する必要がある。これらの設定は、特定のデバイスAPIに対してパーミッションが要求されたときにエンドユーザーに提示される、人間が読める説明です。
UsageDescriptionを含むキーのCocoaキーリストを参照して、アプリに必要となる可能性のあるさまざまな使用説明の設定を確認してください。
詳細については、Appleが提供する「Resolving the Privacy-Sensitive Data App Rejection（プライバシーに配慮したデータアプリの拒否を解決するためのガイド）」に、使用説明を必要とするAPIに関する詳細情報が記載されています。

https://capacitorjs.com/docs/ios/configuration

### Android ```AndroidManifest.xml```

>Android apps manage permissions, device features, and other settings in the ```AndroidManifest.xml``` file, which is located at ```android/app/src/main/AndroidManifest.xml```.

> ```AndroidManifest.xml``` may reference additional files such as ```styles.xml``` and ```strings.xml``` within the ```android/app/src/main/res/values``` directory via @style and @string.

>Androidアプリは、android/app/src/main/AndroidManifest.xmlにあるAndroidManifest.xmlファイルでパーミッション、デバイス機能、その他の設定を管理します。
AndroidManifest.xmlは、@styleや@stringを介して、android/app/src/main/res/valuesディレクトリ内のstyles.xmlやstrings.xmlなどの追加ファイルを参照することができます。

https://capacitorjs.com/docs/android/configuration

## アプリの構成によってはアプリ審査に落ちる可能性がある
→ネイティブ向けにコンポーネントを開発して手当てできる場合はあまり当てはまりませんが、Capacitorをメインで使ったりWebアプリをWebViewでラッピングして提供する場合はUXによってはApple/Google Play Storeのリリース審査で落ちる可能性があります。

zuckeyさんの記事でも言及されていました。
> またWebViewをラップした、いわゆる「ガワネイティブ」については、審査を通さないアップデートや ”アプリらしくない” 挙動があるなどの理由で、審査が急に通らなくなるという懸念が従来よりありました。

リジェクト理由は様々ありそうですが、「挙動がアプリらしくない」や「権限設定と表記が不適切」などが最初につまづきがちです。
ひとまず既存のWebアプリをガワだけでもネイティブ対応させる・・・という場合には注意が必要そうです。

## 3D描画などハイパフォーマンス系の動作
→CapacitorではウェブアプリケーションをWebviewでラップしているので、パフォーマンスチューニングが必要になることがあります。
一応WebGLやCanvas Renderingはサポートされていますが、高負荷処理をさせる場合は注意が必要です。

>Capacitor は、クロスプラットフォーム ゲームを構築するための優れたプラットフォームです。>WebGL とキャンバス レンダリングを幅広くサポートしているため、開発者は最新のモバイル デバイスで高性能なゲーム エクスペリエンスを構築できます。

>開発者はゼロから自由に構築できますが、一般的には、一般的なゲーム オブジェクトと機能のプリミ>ティブを提供するゲーム エンジンを使用して構築することをお勧めします。

https://capacitorjs.com/docs/guides/games


# まとめ
それぞれセットアップからXCode上での動作まで確認した限り、全体的な汎用性やスケール性を重視するなら ```Capacitor```がとてもバランスが良いと感じました。
一方で ```NativeScript``` は開発中のプレビューが非常に容易で、やや不具合はあるもののホットリロードサポートされているので開発体験はそこまで悪くありませんでした。

Capacitorの各機能プラグインはPCでも動作するAPIが割り振られているため、
**「既存Webアプリには影響を最小限にしつつ、ビジネス部門からスピード感持ってネイティブアプリ対応を迫られている」**
のようなケースはCapaticorでとりあえずガワ拡張 & 必要な機能を都度プラグイン追加、スケジュール組めてきたらネイティブコンポーネントとハイブリット化していく・・・という方法ができそうだと思いました。

NativeScriptは各ネイティブコンポーネントや構成の理解が必要になりますが、ネイティブUIコンポーネントを生成してくれるため構造上も「アプリらしくない挙動」という理由でリジェクトされる可能性は低そうです。
テンプレートをWeb/ネイティブそれぞれで管理できるのであれば両画面間の影響を考慮せずに開発できることがメリットだと思いました。

## もっと要約

### スピード・リソース効率重視→Capacitor

### 安全性・審査確度重視→Native Script

という印象でした。

## 最後に

筆者はCapacitorをもう少し調べてiOSアプリリリースまで試してみようと思うので、うまくできたら記事化していきます。
(WebViewでもネイティブ機能かなり使えるようになっているけど、今もリジェクトされることが多いのだろうか？と気になったりしています）

よければ皆さんのVue × ネイティブアプリ開発周りについてもご意見・コメントいただければ嬉しいです！
最後までお読みいただきありがとうございました！