SvelteとIonicで始めるモバイルアプリケーション開発入門 Part1. 導入編

開発環境の構築とプロジェクトの作成

Junya Kawai
nextbeat-engineering
19 min readMar 27, 2023

--

はじめに

こんにちは、ネクストビートでエンジニアをしている川井です。

ネクストビートでは、2022年の4月から、Svelte / SvelteKitを採用し、基幹事業である「保育士バンク!」の技術移行プロジェクトを推進してきました。(詳しくはこちらの記事を読んでみてください。)

もともと、バックエンドをScala + Play Frameworkで作っていたこともあり、フロントエンドも、ScalaベースのテンプレートエンジンであるTwirlを採用していました。また一部のページはAngularVuejQueryなどで書かれていました。

これらの言語、フレームワークからSvelte / SvelteKitに切り替えたことにより、開発生産性が向上し、キャッチアップコストも少なくなったと、多くのメンバーが感じています。

これを受け、モバイルアプリの開発にもSvelteを採用できないかと考え、試行錯誤しながらベースプロジェクトを作成し、「保育士バンク!」のアプリの書き換えをおこなってきました。(もうすぐリリース予定!)

今後、何本かに渡り、モバイルアプリをSvelteで開発する方法についての記事をお届けできればと思います。

プロジェクトの作成

まずは、フロントエンドのビルドツールであるViteを使って、プロジェクトを作成します。

https://ja.vitejs.dev/

プロジェクトのテンプレートにはSvelteTypeScriptの組み合わせであるsvelte-tsを選択します。(※ 詳しくは公式ドキュメントを参照)

# Viteのプロジェクトを作成する
npm create vite@latest svelte-ionic -- --template svelte-ts
Need to install the following packages:
create-vite@latest
Ok to proceed? (y) y

Scaffolding project in /current-path/svelte-ionic...

Done. Now run:

cd svelte-ionic
npm install
npm run dev

コマンドの実行が完了すると、以下のような構造のプロジェクトが作成されます。

tree svelte-ionic -L 1
svelte-ionic
├── README.md
├── index.html
├── package.json
├── public
├── src
├── svelte.config.js
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts

以下のコマンドを実行してアプリケーションを起動します。

# 作成したプロジェクトのtディレクトリに移動
cd svelte-ionic
# npmの依存パッケージをインストールする
npm install
# プロジェクトを実行する
npm run dev

> svelte-ionic@0.0.0 dev
> vite

Forced re-optimization of dependencies

VITE v4.2.0 ready in 2752 ms

➜ Local: http://127.0.0.1:5173/
➜ Network: use --host to expose
➜ press h to show help

http://127.0.0.1:5173/にアクセスすると以下のようなページが表示されます。

プロジェクトの説明

では、作成されたソースコードを見ていきましょう。

重要なところにはコメントを追記しています。

index.html

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Svelte + TS</title>
</head>
<body>
<!-- App.svelteを埋め込むためのコンテナ -->
<div id="app"></div>
<!-- main.tsをmoduleとして読み込んで実行している -->
<script type="module" src="/src/main.ts"></script>
</body>
</html>

アプリケーションのエントリーポイントです。
main.tsを読み込み、export defaultされているapp関数の処理を実行します。

/src/main.ts

import './app.css'
import App from './App.svelte'

/** App.svelte(SvelteのComponent)をid="app"のDOMの内部に埋め込む */
const app = new App({
target: document.getElementById('app'),
})

/** デフォルトで実行される処理として上記のapp関数を設定する */
export default app

index.htmlから呼び出されているファイルです。

export defaultされているapp関数によって、SvelteComponentが作成され、targetの要素をrootとして、ComponentがDOMに埋め込まれます。(※ 詳しくはSvelte公式ドキュメントを参照)

これにより、DOMが以下のような構造に変更されます。

<body>
<div id="app">
<!-- ここにApp.svelteのHTMLテンプレート部分が埋め込まれる -->
<main>
<div>
...
</div>
</main>
</div>
</body>

/src/App.svelte

<script lang="ts">
import svelteLogo from './assets/svelte.svg'
import viteLogo from '/vite.svg'
import Counter from './lib/Counter.svelte'
</script>

<main>
<div>
<a href="https://vitejs.dev" target="_blank" rel="noreferrer">
<img src={viteLogo} class="logo" alt="Vite Logo" />
</a>
<a href="https://svelte.dev" target="_blank" rel="noreferrer">
<img src={svelteLogo} class="logo svelte" alt="Svelte Logo" />
</a>
</div>
<h1>Vite + Svelte</h1>

<div class="card">
<Counter />
</div>

<p>
Check out <a href="https://github.com/sveltejs/kit#readme" target="_blank" rel="noreferrer">SvelteKit</a>, the official Svelte app framework powered by Vite!
</p>

<p class="read-the-docs">
Click on the Vite and Svelte logos to learn more
</p>
</main>

<style>
// 省略
</style>

先ほどDOMに埋め込まれたSvelteComponentです。

Chromeなどのブラウザの開発ツールで実際のDOMを見てみましょう。

上の画像のように、想定通りのDOMの構造になっていますね。

普段はあまり使う機会がないかもしれませんが、このようにして、SvelteComponentをスクリプトからDOMに埋め込むことができます。

サンプルプロジェクトの作成と、初期コードの説明は以上となります。

Ionicの導入

次に、Ionic Framework(以下Ionic)を導入します。

Ionicは、Web標準(HTML, CSS, JavaScript)の技術を使って、モバイルアプリに最適なUIを構築できるSDKです。

モバイルアプリ開発に特化した様々なエコシステムを持ち、後述するCapacitorと組み合わせることで、簡単にモバイルアプリを開発することができます。

また、AngularReactVueなどのモダンなフロントエンドのフレームワークに最適化されたパッケージが配布されており、チュートリアルやドキュメントも豊富に用意されています。フレームワークを使わないVanilla JSのプロジェクトにも組み込み可能です。

(※ 詳しくはIonic公式ドキュメントを参照)

今回は、IonicSvelteのプロジェクトに導入する方法について解説します。IonicにはSvelteの公式のパッケージがないため、Vanilla JSのプロジェクトに導入するのと同じイメージで進めていきます。

インストール

Ionic CLIをインストールし、ionicコマンドを使うことで、簡単にプロジェクトを作成することができます。今回は、既存のプロジェクトにIonicを導入するため、initコマンドを使います。

Project nameにはsvelte-ionicを、Project typeにはcustomを設定します。

# @ionic/cliをグローバルインストール
npm install -g @ionic/cli

# 既存プロジェクトにionicを導入する
ionic init
? Project name: svelte-ionic
[WARN] Could not determine project type.

Please choose a project type from the list.

? Project type: Custom (custom)
[OK] Your Ionic project has been initialized!

初期化処理が完了し、ionic.config.jsonファイルが作成されました。

このファイルにIonicの設定を書いていくことになります。

{
"name": "svelte-ionic",
"integrations": {},
"type": "custom"
}

次に、以下のコマンドで、@Ionic/coreをインストールします。

npm install @ionic/core

これで、開発に必要な準備が整いました。

Ionicの初期化処理

続いて、IonicのComponentを使うために必要な初期化処理を書いていきます。

/src/main.ts

import { initialize }                                                         from "@ionic/core/components"
import { defineCustomElement as defineCustomElementIonApp } from '@ionic/core/components/ion-app'
import { defineCustomElement as defineCustomElementIonNav } from '@ionic/core/components/ion-nav'
import { defineCustomElement as defineCustomElementIonTabs } from '@ionic/core/components/ion-tabs'
import { defineCustomElement as defineCustomElementIonTab } from '@ionic/core/components/ion-tab'
import { defineCustomElement as defineCustomElementIonTabBar } from '@ionic/core/components/ion-tab-bar'
import { defineCustomElement as defineCustomElementIonTabButton } from '@ionic/core/components/ion-tab-button'
import { defineCustomElement as defineCustomElementIonHeader } from '@ionic/core/components/ion-header'
import { defineCustomElement as defineCustomElementIonToolbar } from '@ionic/core/components/ion-toolbar'
import { defineCustomElement as defineCustomElementIonContent } from '@ionic/core/components/ion-content'
import { defineCustomElement as defineCustomElementIonBackButton } from '@ionic/core/components/ion-back-button'
import { defineCustomElement as defineCustomElementIonInfiniteScroll } from '@ionic/core/components/ion-infinite-scroll'
import { defineCustomElement as defineCustomElementIonInfiniteScrollContent } from '@ionic/core/components/ion-infinite-scroll-content'
import { defineCustomElement as defineCustomElementIonList } from '@ionic/core/components/ion-list'
import { defineCustomElement as defineCustomElementIonRefresher } from '@ionic/core/components/ion-refresher'
import { defineCustomElement as defineCustomElementIonRefresherContnet } from '@ionic/core/components/ion-refresher-content'
import App from './App.svelte'

/** CSSのインポート */
import "@ionic/core/css/ionic.bundle.css"

/**
* 初期化処理
*/
const init = (): void => {
/** Ionicの初期化処理 */
initialize()
/** Ionicの各種コンポーネントをカスタムエレメントとして定義 */
defineCustomElements()
/** body直下にApp.svelteを埋め込む */
new App({
target: document.body
})
/** htmlタグにion-ceクラスを追加 (bodyをdisplay:block;に) */
document.documentElement.classList.add('ion-ce')
}

/**
* Ionicのコンポーネントをカスタムエレメントとして定義
* 使いたいコンポーネントだけdefineCustomElementをimportして実行する
*/
const defineCustomElements = (): void => {
defineCustomElementIonApp()
defineCustomElementIonNav()
defineCustomElementIonTabs()
defineCustomElementIonTab()
defineCustomElementIonTabBar()
defineCustomElementIonTabButton()
defineCustomElementIonHeader()
defineCustomElementIonToolbar()
defineCustomElementIonContent()
defineCustomElementIonBackButton()
defineCustomElementIonInfiniteScroll()
defineCustomElementIonInfiniteScrollContent()
defineCustomElementIonList()
defineCustomElementIonRefresher()
defineCustomElementIonRefresherContnet()
}

/** 初期化処理をdefaultとしてexport */
export default init()

まずは、IonicCSSimportを行います。公式ドキュメントには、ReactVueの例が載っています。必要最低限のstyleでよければ、core.cssを選べば良いみたいですね。ionic.bundle.cssには、すべてのIonic Global Stylesheetsが含まれているそうです。今回は、ionic.bundle.cssをimportするようにしています。

次に、initialize関数で、IonicComponentを使うための初期化処理を実行します。

その後、各ComponentdefineCustomElement関数を実行しています。これで、<ion-xxx />のように、HTML内でIonicComponentを呼び出すことができるようになります。

また、body直下にApp.svelteを埋め込むように変更しています。

最後に、htmlタグにion-ceというクラスを付与しています。

これがないと、bodyブロックがdisplay: none;のままになるため、bodyが描画されないみたいです。Angularなどのパッケージでは、画面のちらつき防止策として、初期化処理などが終わった後に.hydratedクラスが付与されるようになっているのかもしれません。

// ionic-framework/core/src/css/structure.scss
html:not(.hydrated) body {
display: none;
}
html.ion-ce body {
display: block;
}

index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Svelte + TS + Ionic</title>
</head>
<body>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

合わせて、index.htmlも少しだけ修正しています。

以上で、一通りの初期化処理が完了しました。

あとは、App.svelteの中身、つまり、アプリケーション部分を自由に書き換えていくだけです。

IonicComponentの使い方や、ルーティング処理の実装に関しては、長くなるので、別の記事で紹介できればと思います。

--

--