「ELMポート」技法を用いたKotlin/JSにおける容易な依存性統合



TLJr : Kotlin/JSから始めて、Firebaseのような大きなNPMライブラリを使うのは難しいと思った.しかし、ELMとそのシステムの過去の経験は、適切な解決策を見つけるのを助けました.The GitHub repo is here
TLあなたは導入をスキップすることができますgo straight into the implementation here .
私は貿易によるバックエンドエンジニアです.しかし、これらの日、私は私の独自のかゆみを傷がなければならない任意のアイデアを簡単にフロントエンドを使用してSupabase 大ファンです.
それは私の親愛なる友人と遊ぶために私に非常に少しの時間を残すことを除いて、それは全くすばらしいですKotlin 😬. そのためにbecause Kotlin Multiplatform hit 1.0 数ヶ月前、私は最後の週Kotlin/JSにダイビングを過ごすことを決めた.
私はそれを記述する多くの時間を費やすことはありませんが、短いKotlin/JSでは、あなたがJavascriptに蒸し出され、それはあなたのフロントエンド(またはバックエンド、その問題のために実行することができます)を書くことができます.今、人々がなぜそれをしたくないか、多くの理由があります.私にとって、それは主に開発者としての私の経験です.私は、長い一日の仕事の後、私自身を楽しんでいたいです.そして、私は開発者の経験で過去にエルムによって台無しにされている少年は、バーが高いと言うに十分です😁.
このブログでは、私の経験の特定の部分に飛び込みたいと思います.使いましょうFirebase 例として.

問題


私のプロジェクトでFirebase AuthとFirestoreを使いたいと言いましょう.図書館からカップル機能へのアクセスが必要です.つまり
// A function to sign up / login
const userCred = await signInWithPopup(auth, provider);

// Another one to log out
await signOut(auth);

// One function to add document
await addDoc(collection(firestore, `users/${uid}/messages`), {
    content : message
});

// one to get the list of messages
const querySnapshot = await getDocs(collection(firestore, `users/${uid}/messages`));
  querySnapshot.forEach((doc) => {
    console.log(doc.data()
});


// finally also a method to automatically get updates are new messages are created to avoid having to fetch messages ourselves
const q = query(collection(firestore, `users/${uid}/messages`));
onSnapshot(q, (querySnapshot) => {
    const messages = [];
    querySnapshot.forEach((doc) => {
        callback(messages);
    });
});


今、私はKotlin/JSからそれらを使用する別の方法を見てみましょう.

外部宣言の使用


External declarations Kotlinが静的型付けを利用できるように、JavaScriptを記述する方法です.最良のユースケースではない.JavaScriptを使用していますが、入力し、Kotlinで.次のようになります.
@JsModule("is-sorted")
@JsNonModule
external fun <T> sorted(a: Array<T>): Boolean
一見したら、それはすばらしい考えのように聞こえる.それが見えるより複雑である以外は.The signInWithPopup メソッドはプロバイダを必要とし、auth API.プロジェクトを基本的に設定する必要があります.JavaScriptでは、簡潔で簡単に:
const firebaseApp = initializeApp(FIREBASE_CONFIG);
const provider = new GoogleAuthProvider();
const auth = getAuth();
const firestore = getFirestore(firebaseApp);
ことは、これらの宣言は、複雑さがたくさん含まれており、記述するために重いです.ここに何firebaseApp コンソールで

コンソール.FirebaseAppのログ.
それは不可能な仕事ではない、私はそれはあまりにも我々は文字通りそれを使用するつもりはないことを与えていると思うfirebaseApp 我々が必要とするものを例示すること以外は、再び.

動的インポートの使用


もう一つのオプションKotlin/JS documentation オファーはdynamic キーワード.キーワードは基本的に型チェッカーを無効にします.
基本的には次のようにします.
val firebase: dynamic = require("firebase/app").default

val firebaseConfig: Json = json(
    "key" to "value"
    ...
)
val firebaseApp: dynamic = firebase.initializeApp(firebaseConfig)
さて、もちろん、それは可能ですが、我々は文字通りの最初の場所で使用するためにすべての理由を文字通り削除していますか?私はどんなタイプでもチェックしません、よりスマートな完成、エラー処理、….
私にとっては、それは単なるオプションではない.

Dukatの使用


私は、Kollin/JSを構築している人々が非常によくその問題に気づいていると思います、そして、彼らは誰もが地球上のあらゆるNPMパッケージのためにアダプターを作成し始めないということを知っています.それは最初の場所で大規模な仕事であり、さらに維持する.さらに、JavaScript生態系のほとんどすべては、彼らのライブラリのためにそれらの日のtypescript定義を提供します.
入るproject Dukat , これは、それらのtypescripts定義を活用して、自動的にそれらからkotlin外部宣言を生成することを目指します.あなたが私に尋ねるならば、素晴らしい考え.次のようになります.
$ npm install -g dukat
$ mkdir test; cd test 
$ npm install firebase
$ dukat firebase/app/dist/app/index.d.ts

これにより、以下のようなファイルが生成された.

Dukatによって生成されるファイルのリスト
実に素晴らしい考えだが、まだ初期の段階にある.これまでのところ、私が変換しようとしたライブラリのどれも実際には動作しませんでした、そして、私は変換されたES 5自体を含む問題を修正しようとする生成されたKotlinファイルで時間を費やしました.楽しくない
その上にthe development of Dukat has currently been paused 新しいIRコンパイラが安定しているので.

ライブラリの使用


もちろん、前にその問題を解決しようとして解決策を提供してきた人々があります.すなわち、firebase-kotlin-sdk ほとんどは私の場合にその問題を解決します.
正直に言えば、それはすばらしく見えます.私はちょうど少しそれを再生しました、そして、それは私のニーズとより多くを満たしました.また、アクティブであり、マルチプラットフォームをサポートしています.
私はなぜその方向に行かなかったのかわからない.二つの主な理由があります.ライブラリの依存性をライブラリに追加したくなかったのです.我々はブラウザで実行され、光を覚えていたいですか?それは4つの機能のためにoverkillのように見えました.その上で、ライブラリは追加の概念を導入し、私は追加のドキュメントに飛び込む必要はありませんでした.kotlin/js自体の学習は十分でした.

これらの良い古いelmポートを覚えていますか?


その時点で、私は数日間オプションの間で苦労していました.私の古い友人とコーヒーを飲んでいるとき、彼は私に「エルムポートでやっているようなことはしないの?」と言った.ハ!過去数日を探していた素敵なオプションがありました.

ステップ・バック、エルム・イントロ


あなたのそれらのためにElm 私が話していることを理解して、私は少し説明しなければならない.ELMはフロントエンドのための純粋な機能言語です.これは、ELMで何かを書くと、実行時例外がほとんどないということです.そしてもちろん、すべてが強く入力されます.
(真剣にも、エルムは私のための経験を吹いている、それを試してください).
今、それは良い音が、それはすべてでJavascriptで素晴らしい再生されません.ELMを書くとき、ランタイムはあなたが安全であることを確認します.しかし、他の端のJavaScriptの世界では、すべての素晴らしいライブラリとAPIの恩恵を受ける;たとえ失敗しても.
この問題を解決するためには、野生のJavaScriptの安全でない世界からニースのきれいな、きれいな世界に橋を架ける方法です.
ポートは基本的には、あなたがJavaScriptと対話することができるように、unbreakableとして定義する強力なインターフェイスを作成することによって完全な書き換えを避けることができます.

そして、我々はKotlinと戻っています


まだはっきりしていないかもしれませんが、私が約束する複雑なものはありません.我々は、我々のKotlinアプリでここのポートの同じ概念を適用します.私たちがすることは
  • 外部宣言を使用してJavaScriptで必要なものの明確なインターフェイスを定義する
  • JavaScript側では、私たちのために重いリフトを行うために薄い層を作成します
  • Kotlin側で、我々が必要としない何でもない安全なタイプされた層を楽しんでください.
  • Kotlinで必要なJavaScript関数に戻りましょう.
    import {FIREBASE_CONFIG} from "./constants";
    import { initializeApp } from "firebase/app";
    import { getAuth, signInWithPopup, GoogleAuthProvider, signOut } from "firebase/auth";
    import { collection, addDoc, getFirestore, getDocs, onSnapshot, query} from "firebase/firestore";
    
    const firebaseApp = initializeApp(FIREBASE_CONFIG);
    const provider = new GoogleAuthProvider();
    const auth = getAuth();
    const firestore = getFirestore(firebaseApp);
    
    const userCred = await signInWithPopup(auth, provider);
    await signOut(auth);
    
    await addDoc(collection(firestore, `users/${uid}/messages`), {
        content : message
    });
    
    const querySnapshot = await getDocs(collection(firestore, `users/${uid}/messages`));
      querySnapshot.forEach((doc) => {
        console.log(doc.data()
    });
    
    const q = query(collection(firestore, `users/${uid}/messages`));
    onSnapshot(q, (querySnapshot) => {
        const messages = [];
        querySnapshot.forEach((doc) => {
            callback(messages);
        });
    });
    
    
    
    その面で、我々は我々が必要とするものだけで、我々自身のAPI層をつくります.我々は、私たちのKotlinアプリで必要な出力を取得します入力.
    const firebaseApp = initializeApp(FIREBASE_CONFIG);
    const provider = new GoogleAuthProvider();
    const auth = getAuth();
    const firestore = getFirestore(firebaseApp);
    
    export async function logIn(){
        const userCred = await signInWithPopup(auth, provider);
    
        return {
            accessToken: userCred.user.accessToken,
            email: userCred.user.email,
            uid: userCred.user.uid,
        }
    }
    
    export async function logOut(){
        await signOut(auth);
    }
    
    export async function saveMessage(uid, message){
        await addDoc(collection(firestore, `users/${uid}/messages`), {
            content : message
        });
    }
    
    export async function getMessages(uid){
        let messages = [];
    
        const querySnapshot = await getDocs(collection(firestore, `users/${uid}/messages`));
        querySnapshot.forEach((doc) => {
            messages.push({
                id: doc.id,
                content: doc.data().content
            })
        });
    
        return messages;
    }
    
    export async function syncMessages(uid, callback){
        const q = query(collection(firestore, `users/${uid}/messages`));
        onSnapshot(q, (querySnapshot) => {
            const messages = [];
            querySnapshot.forEach((doc) => {
                messages.push({
                    id: doc.id,
                    content: doc.data().content
                })
            });
    
            callback(messages);
        });
    }
    
    そして、我々は我々自身の薄いAPI層を宣言することができます
    external interface AppUser{
        val email: String
        val uid: String
        val accessToken: String
    }
    
    external interface AppMessage{
        val id: String
        val content: String
    }
    
    @JsModule("@jlengrand/firebase-ports")
    @JsNonModule
    external object FirebasePorts{
        fun logIn() : Promise<AppUser>
        fun logOut()
    
        fun saveMessage(uid: String, message: String)
        fun getMessages(uid: String) : Promise<Array<AppMessage>>
    
        fun syncMessages(uid:String, callback: (Array<AppMessage>?) -> Unit)
    }
    
    そして、それは本当に、何もないです.私は簡単にテストすることができますJavaScriptの50行を書くことによって、私は私のKotlinアプリでFireBaseライブラリとの相互運用性の必要性を持ち上げた.
    さらに良いことに、私のKotlinもfirebaseへのリファレンスをこれ以上含んでいません🤯. JavaScript側の実装をsupabaseを使用するように変更することができます.Firebaseは実装のアーティファクトになった.

    結論


    それは長いイントロでした、それで、結論の若干の語を書きます😃.
    Kotlin/JSはまだ幼少期ですが、私はフロントエンドを書くために私のお気に入りのバックエンド言語へのアクセスを持つ力が好きです.これからの未来を見ましょう.
    JavaScript生態系との対話はまだ厳しいクッキーです.多くのオプションがあります.私は、「ELMポートメソッド」がいくつかの大きなものを持っていると信じています.
  • コールリン側の妥協はなく、動力学はない
  • 必要はありませんライブラリのインターフェイスには、コトリンの外部宣言を作成するダイビング.
  • 関心の分離の層として使用することができるクリーンなインターフェイス

  • 余分なライブラリの必要はありません、あなたは
  • もちろん、マルチプラットフォームモジュールをビルドする場合や、ライブラリの機能を広範囲に使用したい場合は、あまり面白くないでしょう.
    上記のコードを使用して、サンプル実装を見つけることができますthat GitHub repository .
    私はあなたがポストを気に入ってくれたことを願って、私はあなたが何を考えているか知ってみましょう🎉.
    参考:
  • この投稿のイメージはhere
  • スタックオーバーフローの質問using Dukat with Firebase
  • Someone else using Kotlin/JS with Firebase
  • The very same demo, also from me, but in Elm