辛口情報|クロスエンドシナリオにおけるCtrip Webコンポーネントの実践

著者について

Ctrip のフロントエンド開発マネージャーである Iris は、フロントエンド コンポーネント ライブラリとエンジニアリング分野に重点を置いています。

Ctrip のシニア R&D マネージャーである Abert は、クロスエンド ソリューションに重点を置いています。

1. 背景

H5マーケティング活動を開発した後、通常、マーケティング活動の入口をアプリやミニプログラムを含む複数の端末に置きます。一般的な配信形式には、ネイティブ ページ、React Native ページ、ミニプログラム ページのインライン ポップアップが含まれます。このとき、ネイティブ、RN、ミニプログラムからの人的投資が必要となります。その結果、プロセス全体が、H5 開発のみを必要とするものから、マルチエンド開発とコミュニケーションを必要とするものへと進化し、H5 マーケティング活動の柔軟な立ち上げから、アプリとミニプログラムの対象となるバージョン リリースまで進化しました。

このプロセスを最適化するために、Web コンポーネントのクロスエンド共有という新しいソリューションを導入しました。このソリューションは、「1 セットの Web コードを複数の端末で共有する」というコンセプトに準拠しており、起動サイクルの短縮、人件費の削減、イテレーションへの迅速な対応を目指しています。Webコンポーネントの端末間共有を利用することで、効率的に多端末共有を実現すると同時に、Webコンポーネントをより豊富に表示することができ、ビジネスにさらなる価値をもたらします。

2. プラン紹介

では、「一連のWebコードを複数の端末で共有」を実現するにはどうすればよいのか——

私たちのミニ プログラムは、Taro フレームワークと React フレームワークを使用して開発されています。Taro は HTML タグのレンダリングをサポートしています。この観点から、Web コンポーネントの開発テクノロジ スタックとして React を選択しました。このようにして、一方ではそれを実行できますミニ プログラム側で直接実行することも、一方でミニ プログラム側で直接実行することもでき、React の機能を利用して再利用可能なカスタム HTML 要素を作成できます。

アプレット側では、Web コンポーネントは NPM パッケージの形式で存在します。ネイティブ側と RN 側で、WebView を使用して、Web コンポーネントを含む H5 リンクをロードします。NPM パッケージの形式であっても、Web コンポーネントの形式であっても、それらはすべて同じ Web コード セットの製品です。

実際のプロセスを紹介する前に、Web コンポーネントについて簡単に紹介しましょう。Web コンポーネントは Web 標準の一部であり、W3C によって提案された一連のコンポーネント モデルです。3 つの主要なテクノロジーで構成されます。

a. カスタム要素: 開発者は、独自のプロパティとメソッドを持つことができるカスタム HTML 要素を作成できます。

b. Shadow DOM: 開発者は、カプセル化された DOM ツリーを作成し、それをカスタム要素にアタッチして、スタイルと動作の分離を実現できます。

c. HTML テンプレート: 開発者は、さまざまな Web アプリケーションで使用できる再利用可能な HTML テンプレートを定義できます。

ブラウザはこの標準に基づいた API セットを実装しており、Web コンポーネントの作成者はこれらの API を使用してコンポーネント関数をカプセル化し、競合を気にすることなくどこにでも適用できます。

React または Vue はどちらも対応する API を提供しており、開発者は React コンポーネントまたは Vue コンポーネントの形式で Web コンポーネントを作成できます。ここでは、Web コンポーネントを React コンポーネントの形式で記述し、Web コンポーネントとしてパッケージ化します。

ポップアップ コンポーネントの名前が zt-dialog であると仮定すると、ネイティブ側と RN 側に提供する H5 リンク コンテンツは次のようになります。

<html>
  <head>
    <script src="https://static.tripcdn.com/zt-dialog.umd.js"></script>
  </head>
  <body>
    <zt-dialog></zt-dialog>
  </body>
</html>

このコードは、zt-dialog コンポーネントのカスタム HTML 要素が `zt-dialog` であり、その機能ロジックが UMD 形式の JavaScript ファイルにパッケージ化されていることを示しています。これは、Web コンポーネントを他の H5 に適用できることを意味します。

ミニ プログラムに提供するコンテンツは NPM パッケージ @ctrip/zt-dialog で、主なコンテンツは次のとおりです。

import Dialog from '@ctrip/zt-dialog'
import '@ctrip/zt-dialog/dist/styles/mini.css'

3. Web コンポーネントとホスティング環境

通常の React コンポーネントと比較して、Web コンポーネントではどのような問題を考慮する必要がありますか? Web コンポーネントは異なる環境でホストされているという観点から考えると、ネイティブ側、RN 側、ミニプログラム側がすべてホスト環境になります。

したがって、さまざまなホスト環境を識別する方法、ホスト環境の機能を使用する方法、ホスト環境と通信する方法という 3 つの中心的な問題について考える必要があります。

3.1 ホスト環境の特定

実際には、各エンドで特別なパラメータを渡したり、ミニプログラムとは異なる WebView のグローバル変数を使用してホスト環境を識別して判断したりするなど、さまざまな方法があります。しかし最終的には、環境変数を使用してビルド時に必要なコードのみをパッケージ化するという、より良いソリューションを選択しました。

環境変数は、アプリケーションの実行時にさまざまな環境に基づいてさまざまな値を提供するためのメカニズムです。当社の Web コンポーネントは、プロジェクト内での環境変数の使用をサポートする Vite を使用して構築されています。アプリケーションでは、これらの環境変数は「import.meta.env」オブジェクトを通じてアクセスされ、さまざまな値に基づいてさまざまなロジックが実行されます。ビルド時に、これらの環境変数は静的に置き換えられます。

たとえば、次のソース コードは、「VITE_COMP_TYPE」変数の値に基づいて、異なるホスト環境での onClose イベントと onJump イベントを処理します。

const onClose = () => {
  if (import.meta.env.VITE_COMP_TYPE === 'mini') {
    console.log("mini")
  } else {
    console.log("webview")
  }
});


const onJump = () => {
  if (import.meta.env.VITE_COMP_TYPE === 'mini') {
    console.log("mini jump")
  } else {
    console.log("webview jump")
  }
}

このビルド コマンドにより、次のようになります。

cross-env VITE_COMP_TYPE=mini vite build

ミニ プログラムで使用される NPM パッケージの最終出力は次のとおりです。

const u = () => {
      console.log("mini")
    },
    p = () => {
      console.log("mini jump")
    };

ここには「mini」コードのみがあることがわかります。別の観点から見ると、ミニプログラムで Web コンポーネントを導入する場合、そのサイズは非常に重要なので、この方法を使用してコードをできるだけ小さいサイズでパッケージ化できます。

3.2 ホスティング環境を使用する機能

一般に、Web コンポーネントが使用する必要がある機能には、リクエストの送信、ナビゲーション、共有、ポイントの埋め込みが含まれます。ネイティブ側と RN 側では、WebView を使用して Web コンポーネントをロードします。次に、リクエストを送信するために、ブラウザのリクエスト送信機能を使用できます。埋め込みに関しては、ブラウザを使用して、埋め込みロジックを処理するための埋め込みスクリプトをロードすることもできます。自分たちで; ナビゲーションとブリッジメソッドを使用して共有します。ミニプログラムの面では、さらに考える必要があります。それについては以下で説明します。

一般に、ネイティブ ミニ プログラムはリクエストをカプセル化し、いくつかの特定のリクエスト パラメータを取得し、リクエストの戻り値を前処理するため、リクエストはミニ プログラムからコンポーネント パラメータの形式でのみ Web コンポーネントに送信できます。ナビゲーションや埋没地点についても同様です。

共有は少し特殊で、WeChat ミニ プログラムでは、共有を促進するには 2 つの条件があると規定しています。

条件 1: ボタンコンポーネントに属性 `open-type=share` を設定する。

条件 2: ユーザーがボタンをクリックした後、`Page.onShareAppMessage` イベントがトリガーされ、共有関連情報を取得します。

条件がテストされたら、次のように記述することで Web コンポーネントを満たすことができます。

<button openType="share">
    <p>分享</p>
</button>

2 番目の条件は機能しません。小規模なプログラム開発者であれば、`Page.onShareAppMessage` がページ処理関数であることを知っておく必要があります。これは、ユーザーがページ共有ボタンをクリックしたときのイベントを監視するために使用され、アクティブに使用することはできませんと呼ばれた。この問題を解決するアイデアは次のとおりです

a. Web コンポーネントは、アプレットによって提供される登録センターから一意の共有ソース ID を取得します。

b. Web コンポーネントは、ボタン タグに共有ソース ID を与えます。

c. Webコンポーネントは、このIDに対応する共有情報を共有元情報センターに登録します。

最後に、ユーザーが共有をクリックすると、アプレットは現在の共有元 ID に対応する共有情報を共有元情報センターから取得できます。図:

67828dc3454522f797d8c4786c56c667.png

3.3 ホスト環境との通信

Web コンポーネントはホスト環境と通信する必要があるかという質問について考えてみましょう。もしそうなら、どのようなコミュニケーションシナリオがありますか? 実際には、2 つのシナリオがあることがわかりました。ユーザーがクリックしてコンポーネントを閉じる場合と、適切なタイミングでコンポーネントが表示される場合です。

通信方法は図のとおりです。

1f271ee2a3beda0874fb846f3ea91514.png

「ユーザーが閉じるボタンをクリックする」というシナリオを例として、実際のシナリオに沿って対応するコードを見てみましょう。

const closePopUp = () => {
    if (import.meta.env.VITE_COMP_TYPE === 'mini') {
        props.close(); // 小程序端传递的关闭事件参数
    } else if (isRNWebView() {
        window.postMessage(JSON.stringify({
            closeModal: true  // RN端使用postMessage发送closeModal事件
        }));
    } else if (isNativeWebView()) {
        window.Bridge.insideClose(() => {}); // APP端使用桥方法关闭当前WebView
    }
};

これにより、どのようなシナリオであっても、同様の方法でホスト環境との通信を実現できます。

「適切なタイミングでコンポーネントを表示する」というシナリオを考えてみましょう。まず、「適切なタイミング」とは何かを理解しましょう。特定のビジネスロジックに従うことを前提として、Web コンポーネントを正常に表示させるのはおかしいと思われるかもしれません。 「適切なタイミング」「タイミング」? 実際に実践してみると、ミニプログラム側では、NPMパッケージの埋め込み、パッケージ分離、パブリックスタイル抽出、WebPなどの手法を用いて、パフォーマンスを可能な限り最適化することができ、実際にWebコンポーネントは正常に表示されており、ユーザーがコンポーネントのロードを認識しないようにするために正確です。

ただし、Native および RN 側では、WebView を使用して H5 リンクをロードします。大きな画像 + 表示アニメーションを使用すると、Web コンポーネントのプレゼンテーションは、主にユーザーが大きな画像をはっきりと認識できるという点で、やや満足のいくものではありません。完了アニメーションが表示される前に画像が開始されています。したがって、このシナリオはより慎重に扱う必要があります。

処理のアイデアは次のとおりです。

a. ネイティブは WebView コンテナをロードしますが、現時点では WebView は表示されません。

b. WebView がロードされた後、H5 をロードします。この H5 は、より時間のかかるリソースをロードします。

c. リソースのロードが完了すると、H5 はネイティブに WebView を表示するように通知します。

d. H5 は Web コンポーネントを表示し、Web コンポーネントのアニメーションを開始します。

図:

f4bb878ac547569147c392400caa6480.png

リソースロード後の「NativeにWebViewを表示するように通知する」処理はブリッジメソッドの通信機構を使用します。

これにより、Native側とRN側でWebコンポーネントの表示をより詳細に制御できるようになり、Webコンポーネントをより美しく表示できるようになります。

この時点で、Web コンポーネントとホスティング環境の間の中心的な問題は解決されます。このとき、ミニ プログラムで小さなスタイルの問題も発生しました。Taro が px サイズの単位を変換するときのデフォルトの変換基準は 750px ですが、Web コンポーネントを作成するときは通常 375px を標準として使用します。その結果、ミニプログラム側で表示すると、全体のスタイルがミニプログラムスタイルの2倍の大きさになってしまうので、最終的な解決策は、ミニプログラムスタイルをコンパイルする際に、プラグインを使用してサイズ*2をペアにすることです。

さらに、画像の読み込みパフォーマンスを最適化するために、Web コンポーネントの画像は webp 形式を使用します。ミニプログラム側ではwebpがサポートされているのでそのまま利用できますが、NativeやRN側ではブラウザのサポート状況に応じて判断する必要があります。

4. Web コンポーネントのサポート

「複数の端末で共有される一連の Web コード」を開く正しい方法を理解した後、各端末が Web コンポーネントに対してどのようなサポートを提供する必要があるかを見てみましょう。結局のところ、他の人の立場になって考えて初めて、「観察者の視点」から Web コンポーネントを改善することができます。

まず、ネイティブ側で Web コンポーネントの透過的な WebView を開きます。この WebView は、非透過的な WebView とは区別する必要があります。したがって、特定のクエリ パラメータを H5 リンクに追加することが合意されています。Web コンポーネントが WebView の幅と高さを指定したい場合は、特定のクエリ パラメータも追加します。

合意されたクエリパラメータが「insidepop=1」であると仮定すると、zt-dialog コンポーネントの H5 リンク形式は次のようになります。

https://m.ctrip.com/demo/zt-dialog.html?insidepop=1

Android を例に挙げると、ネイティブ側で使用されます。

Intent intent = new Intent(); // 初始化一个通用Intent
Activity activity = new Activity();
intent.setClass(activity, H5Container.class)
intent.putExtra(H5Container.URL_LOAD, 'https://m.ctrip.com/demo/zt-dialog.html?insidepop=1'); // 加载包含Web组件的H5链接
AppUtil.startActivity(activity, intent);

さらに、RN 側では、WebView コントロールを使用して透過的な WebView を開きます。ポップアップを閉じる、ナビゲーション、共有などの機能を処理する必要があるため、RN 側は WebView コントロールに基づいて再度カプセル化されます。

これは、RN 側で使用される zt-dialog コンポーネントの H5 リンク形式でもあります。

import React from 'react';
import { ViewPort, Text, TouchableHighlight } from 'react-native';
import { WebViewModal } from 'react-native-webview';


export default class Demo {
  render() {
    return (
      <ViewPort>
        <TouchableHighlight onPress={() => {this.webviewRef.showModal()}}>
          <Text>show modal</Text>
        </TouchableHighlight>          
        <WebViewModal
          position='bottom'
          webViewUrl={'https://m.ctrip.com/demo/zt-dialog.html'}
        />      
      </ViewPort>
    )
  }
}

最後に、ミニ プログラムは NPM パッケージの形式を使用します。上記の考慮事項に基づいて、ミニ プログラムの機能の多くはパラメーター転送の方法に依存します。そのため、ミニ プログラムは React Hoc コンポーネントをカプセル化して合意を形成します。 、ナビゲーション、共有、その他の機能はすべてこの Hoc コンポーネントにカプセル化されています。この Hoc コンポーネントは次のようになります。

import React from "react"
import Taro from "@tarojs/taro"


const webBridgeHoc = (WebComp)=>{
  return (props)=>{
    return <WebComp
      _ubtTrace={_ubtTrace} // 埋点
      _request={requestFunc} // 小程序原生request
      _navigateTo={Taro.navigateTo} // 跳转
      _redirectTo={Taro.redirectTo} // 重定向跳转
      _reLaunch={Taro.reLaunch} // 关闭所有页面,打开到应用内的某个页面
      _switchTab={Taro.switchTab} // 切换tab页
      ...
    />
  }
}
export default webBridgeHoc

zt-dialog コンポーネントがアプレットで使用されている場合:

import Dialog from '@ctrip/zt-dialog'
import '@ctrip/zt-dialog/dist/styles/mini.css'
import webBridgeHoc from '@/components/webBridgeHoc'
 
export default webBridgeHoc(Dialog)

一般に、各エンドでの Web コンポーネントのサポートは比較的簡単です。ある程度のカプセル化を行った後、実際のアプリケーション処理中に、Web コンポーネントの H5 リンクを配信するために、ネイティブ側のホームページのポップアップ ウィンドウをサーバー側で閉じる処理をさらに実装しました。したがって、ネイティブ側のホームページ ポップアップ ウィンドウは、ネイティブ側で人間の介入を必要とせず、完全な閉ループの需要配信サイクルを完了できます。このプロセスはミニプログラムとRN側に完全にコピーできます。この時点で、ネイティブ、RN、小規模プログラムの人的資源は完全に解放されます。

5. まとめと展望

実際、Web コンポーネントに対する各端末のサポートから、Web コンポーネントの端末間共有は、一方では各端末の既存の機能を統合し、他方では豊富な機能などの独自の利点を活用していることがわかります。ユーザーを惹きつけるアニメーション。言い換えれば、実践の初期段階では、投資コストは大きくありませんが、初期の利益は直感的です。複数のソースから人員が解放され、利点を最大限に活用して利益を生み出すことができるかどうかが、支払いを継続する必要があるということです。 Webコンポーネント開発における注意点。

今後も、Web コンポーネントのリッチな表現がユーザーのクリックスルー率と各エンドの Web コンポーネントのパフォーマンスを効果的に向上させることができるかどうかに注目していきます。

最後に、Web コンポーネントの効果を見てみましょう。

ネイティブ側:

31b04fc06cd138e3e787fbe9e1a9be1a.gif

ミニプログラム:

0520d0936a332aed6d7f58c391ac6292.gif

興味のある Web 開発者からの貴重なコメントをお待ちしております。

【おすすめの読書】

80f934b4ee6cdfb53d1ef67a29f4b2ee.jpeg

 「Ctrip Technology」公開アカウント

  共有、コミュニケーション、成長

おすすめ

転載: blog.csdn.net/ctrip_tech/article/details/130998313