STORES 第二のWeb Components実用事例

>100 Views

October 16, 24

スライド概要

New Engineering: Spin-off https://hey.connpass.com/event/330050/ の発表資料です

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

(ダウンロード不可)

関連スライド

各ページのテキスト
1.

STORES 第二のWeb Components実用事例 New Engineering: Spin-off 太田一輝 (@otariidae) STORES 株式会社

2.

自己紹介 ● 太田一輝 (@otariidae) ● 2024年4月 新卒入社 ● 日々の業務では STORES ブランドアプリ や STORES ロイヤリティ のRailsを書いている 2

3.

先日のNew Engineeringにて https://speakerdeck.com/tttttahiti/make-data-analysis-portable-as-a-component-library STORES、Web Componentsやってます 3

4.

Web Components事例、まだあります 第二の事例をお話しします

5.

前書き 対象 ● Web Componentsが気になる人 こんな話をします ● 最近の開発でWeb Componentsがうまくハマった事例のストーリー こんな話はしません ● ● Web Components自体の技術的な解説 Web Componentsの未来 5

6.

目次 01 課題の背景 02 解決策の模索 03 たどり着いた解 04 Pros/Cons 05 まとめ 6

7.

課題の背景:STORES ロイリティとは 7

8.

課題の背景:求められる要件 顧客がShopifyのショップのサイトを開くと STORES ロイヤリティ固有の顧客データを表示させる ● ● ● ● バーコード ランク ポイント数 ... 8

9.

課題の背景:App Blocks App Blocks:Shopifyテーマにアプリ独自の要素を埋め込める仕組み 公開 App Block アプリ開発者 インストール ストア マーチャント 顧客 9

10.

課題の背景:App Blocks App Blocks:Shopifyテーマにアプリ独自の要素を埋め込める仕組み 図の引用元: https://www.shopify.com/in/partners/blog/theme-app-extensions 10

11.

課題の背景:App Blocksのつくりかた {% assign point = customer.metafields.point.value | at_least: 0 %} {{% if point > 0 %}} <p> {{ point | money_without_currency }}ポイント </p> {{% endif %}} 11

12.
[beta]
課題の背景:App Blocksのつくりかた

Liquid
<p
class="rank"
data-customer-id="{{ customer.id }}"
>
現在のランク: <span class="rank__text"></span>
</p>
<p class="error-message"></p>

JavaScript
const customerId =
document.querySelector("rank").dataset.customerId;
const $rankText = document.querySelector(".rank-text");
const $errorMessage =
document.querySelector(".error-message");
if ($rankText === null || $errorMessage === null) {
throw new Error("要素が存在しません");
}
try {
const response = await fetch(
`http://example.com/customer/${customerId}/rank`,
);
if (!response.ok) {
$errorMessage.textContent = "取得に失敗しました";
return;
}
const json = await response.json();
$rankText.textContent = json.rank;
} catch (error) {
$errorMessage.textContent = "取得に失敗しました";
return;
}
12

13.
[beta]
課題の背景:App Blocksのつくりかた

Liquid
<p
class="rank"
data-customer-id="{{ customer.id }}"
>
現在のランク: <span class="rank__text"></span>
</p>
<p class="error-message"></p>

JavaScript
const customerId =
document.querySelector("rank").dataset.customerId;
const $rankText = document.querySelector(".rank-text");
const $errorMessage =
document.querySelector(".error-message");
if ($rankText === null || $errorMessage === null) {
throw new Error("要素が存在しません");
}
try {
const response = await fetch(
`http://example.com/customer/${customerId}/rank`,
);
if (!response.ok) {
$errorMessage.textContent = "取得に失敗しました";
return;
}
const json = await response.json();
$rankText.textContent = json.rank;
} catch (error) {
$errorMessage.textContent = "取得に失敗しました";
return;
}

つらい🥺

13

14.

課題:LiquidとJavaScriptの併用 テンプレートやロジックがLiquidとJavaScriptに分散して 見通しが悪く、つらい querySelector, textContentがJavaScriptのコードの大半を占め まるでDOMパズル どことなくjQueryの時代を彷彿とさせる... 14

15.

課題:LiquidとJavaScriptの併用 テンプレートやロジックがLiquidとJavaScriptに分散して 見通しが悪く、つらい querySelector, textContentがJavaScriptのコードの大半を占め まるでDOMパズル どことなくjQueryの時代を彷彿とさせる... 💡全てをJavaScriptに寄せればいいのでは?💡 15

16.

解決策の模索:App blocksの制約 全てをJavaScriptに寄せるライブラリたち 代表例 16

17.

解決策の模索:App blocksの制約 全てをJavaScriptに寄せるライブラリたちは使い難い 17

18.

解決策の模索:App blocksの制約 App BlocksにおけるJSの推奨サイズ:10 KB [1] [1] https://shopify.dev/docs/apps/build/online-store/theme-app-extensions/configuration#file-and-content-size-limits 18

19.

たどり着いた解 ライブラリなしでJavaScriptでコンポーネントをつくる方法 💪💪💪Web Components💪💪💪 19

20.

たどり着いた解 方針 ● ● Liquidはカスタム要素に属性値を渡すだけに徹する テンプレートとかロジックとかもろもろはJavaScriptでやる 20

21.
[beta]
具体的なコード例:カスタム要素を定義する

Liquid
<dokuji-element
point="{{
customer.metafields.point.value }}"
></dokuji-element>

JavaScript
class DokujiElement extends
HTMLElement {
// ...
connectedCallback() {
// さまざまな処理
this.innerHTML = `
<p>HTML文字列</p>
`
}
}

21

22.
[beta]
具体的なコード例:ShadowDOMを使ってみたり

class DokujiElement extends HTMLElement {
// ...
constructor() {
super();
this.attachShadow({ mode: "open" });
}
connectedCallback() {
// さまざまな処理
this.shadowRoot.innerHTML = `
<style>
:host { display: block }
.red { color: red }
</style>
<p class="red">HTML文字列</p>
`;
}
}
22

23.
[beta]
具体的なコード例:状態管理してみたり

class DokujiElement extends HTMLElement {
#state = { status: "unsent" };
setState(newState) {
this.#state = newState;
this.render();
}
async connectedCallback() {
this.setState({ status: "loading" });
const response = await fetch("https://example.com");
this.setState({ status: "loaded", data: await response.json() });
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host { display: block }
</style>
<p>${
this.#state.status !== "loaded" ? "ロード中" : "いい感じに構築したHTML文字列"
}</p>
`;
}
}

23

24.

良くなったところ ● テンプレートエンジンをJavaScript側に一元化 ● ライフサイクルが提供されている ● JavaScriptの豊富な表現力を存分に使える ☺ うれしい ☺ 24

25.

むずいところ ● 定型的な記述量が多い ○ ● 足りないところを自前で書く必要がある ○ ● ライフサイクルメソッドとか 状態管理とか インタラクティビティ ○ ReactみたいにonClickでシュッと書けない 25

26.

むずいところ ● 定型的な記述量が多い ○ ● 足りないところを自前で書く必要がある ○ ● ライフサイクルメソッドとか 状態管理とか インタラクティビティ ○ ReactみたいにonClickでシュッと書けない ⚠ Web Componentsを生で使うときの話 ⚠ 26

27.

今回の流れまとめ 1. App Blocksで動的なブロックをつくろうとすると LiquidとJavaScriptがつらい 2. Liquidを薄くしてJavaScriptに寄せよう 3. ライブラリは使えないよ 4. Web Componentsを使うと便利 27

28.

伝えたいこと ● ShopifyのApp Blocks開発にWeb Componentsが便利! ● 弱点を知りつつみんなも使おう! 28

29.

良いWeb Componentsライフを!