🧙‍♀️

『WebサイトにHTMLを追加できるWebサービス』を個人開発した話(Deno, Firebase, Azure)

2022/11/20に公開

いや、HTMLを追加できるなんて当たり前じゃんなんてツッコまれそうです。

サービスの紹介と、なぜ開発したのか、どうやって開発したのかの話を書いていきます。かなり特殊な開発方法だと思いますので、興味を持っていただけたら嬉しいです。

サービスの感想などフィードバックいただけたら飛び上がって喜びます。

作ったもの

https://jitoin.com

何ができるのか

Jitoinにログインして生成した5行のタグ(画面右)をWebサイトに追加しておけば、作ったコンテンツ(画面左)を対象サイトに反映させることができます。

タグ管理画面

エディタでコンテンツを作れる

HTMLエディタでアルタイムプレビューを見ながらコンテンツを作ることができます。CSSの命名規則は対象サイトに合わせる必要はありません。idもclassも好きに名付けてOKです。

エディタ画面

テンプレートからコンテンツを追加できる

いくつかテンプレートを用意していますので、画像と文章を差し替えるだけでキャンペーンバナー等をWebサイトに追加できます。
テンプレートはまだ少ないので増やしていきたいです。

配信期間を設定できる

コンテンツをいつからいつまで表示するか、設定できます。

配信期間

追加する位置を設定できる

CSSセレクタの知識が必要になりますが、CSSセレクタでどこに表示するのか変更できます。

また、カスタム要素にもできますので、<custom-element></custom-element>のように指定した名前のタグを書くだけで、書いた位置にコンテンツを表示します。

アクション

どのタイミングで表示するか設定できる

クーポンなどは、長時間見ている人だけに表示させたいときがあります。何秒後にコンテンツを表示するのか調節できます。
まだ未実装ですがスクロール量やクリックイベント発生時などのタイミングを検討しています。

トリガー

パラメータを利用できる

パラメータとして変数を利用できます。パラメータを設定しておけばHTMLを触らなくてもカスタマイズができるようになります。
もし、パラメータが書き換えられた場合、新しいパラメータに合わせてコンテンツが再描画されます。

パラメータ

モジュールを利用できる

モジュールとして、定義済みの関数や外部ライブラリを利用できます。
現在は次の3つだけですが、増やしていきます。

モジュール

なぜ作ったのか

このサービスを開発した理由としては、正直に言うと、手っ取り早くサービスを作って稼ぎたい!という不埒な動機から始まっています。

私はよくShopifyを含むECサイトの運用に携わることが多く、Shopifyアプリも作ったことがありました。
Shopifyアプリは日本企業の参入がまだ少ない(と思っている)ので、Shopifyアプリで何か作れば大したものじゃなくてもお金が入ってくるのではと安易に考えました。

考えた結果、ECサイトの運用で意外と労力を使う、ポップアップ表示をするアプリを作ろうと至りました。私が自分で開発しているJitoライブラリを使えば、既存のHTML・CSSに影響を受けずにポップアップを追加できるので、簡単にサービスを作ることができます。それを自分と同じエンジニアに便利に使ってもらえたら嬉しい限りです。

そう思ったのですが、調べてみるとすでにShopifyには優秀なポップアップアプリが沢山あったので、作る意味無いじゃんと断念しかけました。

しかし、よくよく考えると、JitoはポップアップどころじゃなくWebサイトを魔改造してしまうライブラリです。

ならば、Jitoをそのままサービスにしてしまえるのではと考えました。私はエンジニア向けのサービスを開発しているのですが、先のポップアップアプリ達は、エンジニアからすると、やってることの割にお値段高めなので使いづらいと思います。しかし、HTMLを自在に編集できたらどうでしょう。需要あるかもしれません。

同じようなサービス(Webコンポーネントを用いたサービス)を探してみたのですが、見つからなかったので、やるなら今だと急いで開発しました。見つけきれなかっただけで他にあるかもしれませんが。。。

Shopifyアプリは経験上、繊細で管理に気をつかうので、まずはGoogleログインだけでWebサービスとして実装して、Shopifyアプリにするかどうかは、需要を見てから判断しようと思っています。

どうやって作ったのか

開発期間は約1ヶ月半、いくつかのクラウドプラットフォームを駆使して作りました。

ほとんどが従量課金のため、開発時点ではほぼ無料で構築しています。
開発時点でお金を使ったのはドメインの購入代金と、Deno Deployだけです。Deno Deployは別の用途にも使っていましたので有料プランにしていますが、これも無料プランがあります。

次の2点にこだわりました

  • シンプルな開発環境
  • グローバル分散

Jito

HTMLをWebサイトに追加する役割を担っているのは、Jitoです。Vue.jsの代替として作ったライブラリです。

https://github.com/ittedev/jito

https://zenn.dev/itte/books/5ce6aac9166aed

開発した動機で書いた通り、Jitoinは単にJitoをWebサービスにしただけで、サービス名の由来もJitoとJoinをかけただけです。

JitoはHTMLライクなテンプレートエンジンを持っていて、テンプレートを解析して仮想DOMにしてWebサイトに差し込みます。

テンプレートエンジン内で<script>タグは動きませんが{{ }}内やonclick=""等でいくつかのAPIや変数にアクセスできますので、少しであれば動きも付けることができます。
そして、シャドウDOMとしてコンテンツを展開しますので、CSSの命名規則を対象のWebサイトに合わせる必要もありません。

Jitoのおかげで、本サービスを短期間で作れました。ただ、Jitoそのものは1年前から作っています。。

ファイルストレージ(&CDN)

本サービスで重要なのは、ユーザーのWebサイト内に効率よくコンテンツを読み込むことです。これにはCDNが不可欠です。Azure CDNを使いました。

https://azure.microsoft.com/ja-jp/pricing/details/cdn/

AzureにはFront Doorという高度なCDNもあるのですが、そこまでの機能は今のところ必要無いため使っていません。

Azure CDNはAzure Blob Storageにファイルをアップロードすれば、それをCDNで配信してくれます。
Blob StorageへのアップロードはRest APIが用意されいますし、Storage Explorerを使えばドラッグ&ドロップでアップロードができます。簡単にMicrosoft提供のCDNが使えるのは便利です。

しかも、独自ドメインを利用できます。

フロントエンド

管理画面のフロントエンドはフレームワークとしてJitoを用いました。Jitoをユーザーのサイトに突っ込むサービスなのに公式サイトでJitoを使ってなかったら笑われますからね。

実はJitoはまだSPAに向いていません(いくつか課題があるのですが時間が無く・・・すみません)。その代わり、事前コンパイル無しで動的なページを作るのに向いています。
よって静的サイトと同じようにHTML、CSS、JS、画像等の静的ファイルを置いているだけです。

作った静的ファイルはFirebase Hostingにホスティングしています。

https://firebase.google.com/docs/hosting

firebase deployと打つだけで、Webサイトをグローバル分散してくれます。

間違えてコマンドを打ってしてしまう恐れもありますので、簡単すぎるのは逆にどうかと思いますが、まだサービスは個人開発のBETA版ですのでスピード重視で問題ないでしょう。

その他のライブラリ等はcdnjsSkypackからインポートしています。

サーバーサイド

Firebaseにホスティングしているのであれば、Cloud Functionsが第一候補になりますが、利用せずDenoで開発しました。

https://deno.land/

DenoはTypeScriptのファイルをトランスパイル無しでそのまま実行できます。また、モジュールのインストールコマンドを打たなくても、ソース上にESMでURLを記述すれば利用できます。これはNode.js無しのフロントエンドJavaScriptと同じ動きです。

フロントエンドと同じ書き方で、書いたらそのままファイル1つで動くというのがDenoの素晴らしいところだと思います。

作ったプログラムはDeno Deployにデプロイしています。

https://deno.com/deploy

Deno DeployはGitリポジトリと連携すればpushするだけで作ったサーバーを世界中に分散配置してくれます。何度も書きますが個人開発のBETA版だから許される便利さです。

Denoのフレームワークは使っていません。
Denoにはサーバーサイドフレームワークが色々ありますが、まだこれだ!というものに出会えていないというのもあります。Denoはフレームワークだけでなく、サードパーティモジュールが全体的に未熟です。npmモジュールへの対応も進んでいますので、Deno向けに作られたモジュールは淘汰される可能性もあります。まだ待ちかなと思います。

とはいえ、そもそもDenoはサードパーティモジュールがそこまで重要ではありません。フロントエンドと同じfetchが最初から使えますし、Webサーバーもフロントエンドと同じRequestResponseインターフェースを使って作れます。
私がよく使うDay.jsのようないくつかのライブラリはSkypackからフロントエンドと同じ方法でインポートして使えます。フロントエンドエンジニアであればすぐにDenoに慣れると思います。

開発環境

自分のWindowsにscoopでdenoとfirebaseをインストールして、VSCodeを使って開発しています。

プロジェクトディレクトリはこうなっています。シンプル!

ファイル構成

フロントエンドもサーバーサイドも、面倒なライブラリのインストールやトランスパイル無しで、開発できる時代になりました。

感覚的にはcomposer普及前のPHP開発に近いですね。言語をインストールして、テキストファイルにプログラムを書いたらそれだけで動く。パッケージ管理システムの普及で失われたその環境が、ようやく戻ってきたのだと思っています。

そして、自分のPCでテキストファイルを編集したら、コマンド一発で世界中に分散して公開できるのです。無料で。

この開発環境実現のためにDenoとJitoにこだわったというのが本音です。
何ならCodespacesを使えばWebブラウザだけでそれができてしまいます。

データベース

データベースにはAzure Table Storageを使いました。

https://learn.microsoft.com/ja-jp/azure/storage/tables/table-storage-overview

もともとはFirestoreを使っていたのですが、Deno Deployとの相性があまり良くなくて乗り換えました。おかげでコードが半分になりましたので良かったと思います。

Table Storageは、Firestoreのように階層を作ったりRDBのようにクエリを書けません。そのうえキーが2つあるクセのある構造です。しかし、本サービスのように複雑なDBを必要としない場合は設計が難しくありませんし、何より安いです。

そして、もし、収益を得られるようなサービスになったら、Cosmos DBに移行してグローバル分散できます。

ユーザー認証

認証はJWTの認証サーバーを作成しました。この規模のシステムで認証機能を分ける必要は無いのですが、もともとはユーザー認証サービスを使いたかったのでそれに合わせた形です。

ユーザー認証サービスを使わずに自作に至った経緯として、最初はFirebase Authenticationを利用しました。

https://firebase.google.com/docs/auth

サービスの利用者はWebコーダーを想定していてGoogleアナリティクスをよく使っているだろうと推測しましたので、とりあえずGoogleログインを実装したかったのです。Firebaseの良いところは公式マニュアルが充実していることですね。しかもGoogleログインをボタン一つで追加できるので、とにかく簡単に認証機能を作れます。

しかし、デメリットは初回認証に時間がかかることです。ページを開いてからログインしているかどうかの判定に0.6秒程度かかります。
とはいえ、そもそも、セッション認証ではなくJWT認証を採用する理由は、APIひとつひとつの認証のためにわざわざセッションストアにアクセスするというほんの少しの時間を短縮するためです。時間がかかっているのはアクセストークンを取得する処理ですが、一度取得してしまえば後は問題ありません。
ですので、SPAにすれば許容できるかなと思っていたのですが、0.6秒がなぜか突然25秒かかるようになりました。 これはさすがに許容できませんでした。理由は分かりませんが、毎回25秒ですので意図的にそうされているように思います。SPAではなかったため、認証しすぎて制限でもかかったのでしょうか。

そこで何度もAuth0への乗り換えを検討しました。しかしAuth0の料金は高額ですので、収益が得られる確証が無い限り個人開発には向いていないと思います。

ちなみに、Azure Identityもチャレンジしましたが、なかなか導入できず、断念しました。ログイン画面を表示するところまでは簡単なのですが、その先がネットに情報も少なく、公式マニュアルは解読できず・・・。誰か使ってる人いたらぜひ記事にしてください。

最終的に認証サーバーを自作することにしました。同じくDeno Deployにデプロイしています。鍵の管理はAzure Key Vaultを使っています。

https://learn.microsoft.com/ja-jp/azure/key-vault/general/overview

Key Vaultを使えば、秘密鍵と公開鍵のペアを作ってくれます。しかも自動ローテーション機能で一定期間ごとにキーペアを更新してくれます。秘密鍵は取得できませんが、REST APIを使って署名できます。つまりはFirebase AuthenticationのIDトークン署名の役割を担ってくれます。

ただし、Key Vaultはグローバル分散ではありませんので、グローバル化するときには、認証サーバーとKey Vaultとの距離がボトルネックになります。Firebase Authenticationの二の舞です。

そのときは、Key Vaultをいくつか用意して、認証サーバーは一番近いKey Vaultで署名を行い、アプリケーションサーバーは全Key Vaultから取得しておいた公開鍵で検証を行うという方法になると思います。
Deno DeployはDENO_REGION環境変数でデプロイされたリージョンを取得できますので、自身から最も近い場所にあるKey Vaultの公開鍵から順番に検証を行えば、ほぼ一発で検証をパスできるでしょう。

認証サーバーのデータはアプリケーションサーバーと同じくAzure Table Storageに保存しています。これでCosmosDBに移行すればグローバル分散した認証サーバーになるはずです。

コードエディタ

コードエディタは自作しました。

本当はMonaco Editorを使いたかったのですが、モバイル対応していないし、表示とカーソルが合わなくなるしで使えませんでした。Monaco Editorの第一優先はVSCodeでしょうから、モバイル対応なんていつになるか分かりません。

Ace、CodeMirror、CodeJarなど他のコードエディタライブラリも試しましたがどれも、私の特殊な環境を想定していないのか、使えませんでした。

仕方なくの簡易版自作ですので、機能が少なく重いです。テキストエリアにPrism.jsを重ねているだけの単純構成です。同じ手法のエディタがPrism LiveIBLIZEなどあります。
どれも同じ発想でコードエディタを実現しているので、動きが鈍いです。

いずれは、何とかしてMonaco Editorを導入したいと思います。

https://microsoft.github.io/monaco-editor/

何千行というコードは想定していませんので、自作エディタでも速度は許容できるし工夫すれば改善が見込めます。しかし、CSSを書くにあたってインテリセンスとカラーピッカーの有無が生産性を大きく左右します。VSCodeはエディタシェアNo1ですので慣れている人が多いはずですし。

リアルタイムプレビュー

リアルタイムプレビューは、<iframe>にJitoのコンポーネントを突っ込んでいます。いちいちコンポーネントを再生成して差し替えているので、更新するたびに画面がチラつきます。
リアルタイムプレビューと言い切るにはちょっと微妙ですね。innerHTMLを書き換えるのと大差ありません。

JitoがSPAに向いていない理由の一つはこれで、コンポーネントを差し替えるとコンポーネント内のDOMを一から再描画してしまいます。ページを切り替えるたびにDOMを再描画していたらSPAのメリットが半減です。Jitoの次期バージョンではコンポーネントを差し替えても仮想DOMを引き継ぐようにするつもりです。そうすれば書き換えた部分だけが反映されるリアルタイムプレビュー(ドヤァ)になるはずです。

副産物

Azure StorageおよびKey VaultをDenoで使うためにREST APIの薄いクエリビルダを作りました。本サービスに必要だった機能しか実装していませんが、よければご利用ください。

https://github.com/itte1/azure_storage_client

感想

さくっとWebサービスを作ろうと思ったらFirebaseが便利で土台はすぐできました。しかし、細かい調整をしていると、最終的にHosting以外はAzureメインになってしまいました。Firebaseは導入のしやすさがピカイチなのですが、痒い所に手が届かなくて、それが何年も放置されていて、あとほんの少し努力してくれたら最高のプラットフォームになると思うのですごく残念です。Webサービスよりはアプリへの需要を優先しているのかもしれません。

なぜAWSやGCPではなくAzureなのかというと、今回使っていませんがCosmosDBの無料枠が魅力的だったからです。使ってみて、Azureは以下の理由から導入敷居が高いと思いました。個人の感想です。

  • 日本語情報が少ない
  • 公式ドキュメントが難解
  • サブスクリプションやリソースグループ等の概念が分かりずらい
  • Azure ADが分かりにくい上にADと付くサービス群が何かと他のサービスに絡んできてややこしい

これを何とか突破すれば、料金面、機能面は優秀だと思いました。特に今回利用したBlob StorageTable StorageKey Vaultは接続さえできれば、何を作るにもこれだけでいいいんじゃないかと思うくらい便利です。

今回のサービスは、将来Shopifyアプリにして海外の人にも使ってもらえたら嬉しいと思っていたので、グローバル分散を重要視して、様々なクラウドプラットフォームを使って開発しました。国内向けに個人開発するならVPS一個から始めて良いと思うのですが、グローバル利用を想定して作るのは勉強になりますし、楽しかったです。
グローバル分散した意味があったのかは、ユーザー数が増えないと分からないので需要次第ですね。そうなったらスケールアウトも必要になるかもしれません。

Discussion