>100 Views
October 16, 24
スライド概要
New Engineering: Spin-off https://hey.connpass.com/event/330050/ の発表資料です
Software engineer
STORES 第二のWeb Components実用事例 New Engineering: Spin-off 太田一輝 (@otariidae) STORES 株式会社
自己紹介 ● 太田一輝 (@otariidae) ● 2024年4月 新卒入社 ● 日々の業務では STORES ブランドアプリ や STORES ロイヤリティ のRailsを書いている 2
先日のNew Engineeringにて https://speakerdeck.com/tttttahiti/make-data-analysis-portable-as-a-component-library STORES、Web Componentsやってます 3
Web Components事例、まだあります 第二の事例をお話しします
前書き 対象 ● Web Componentsが気になる人 こんな話をします ● 最近の開発でWeb Componentsがうまくハマった事例のストーリー こんな話はしません ● ● Web Components自体の技術的な解説 Web Componentsの未来 5
目次 01 課題の背景 02 解決策の模索 03 たどり着いた解 04 Pros/Cons 05 まとめ 6
課題の背景:STORES ロイリティとは 7
課題の背景:求められる要件 顧客がShopifyのショップのサイトを開くと STORES ロイヤリティ固有の顧客データを表示させる ● ● ● ● バーコード ランク ポイント数 ... 8
課題の背景:App Blocks App Blocks:Shopifyテーマにアプリ独自の要素を埋め込める仕組み 公開 App Block アプリ開発者 インストール ストア マーチャント 顧客 9
課題の背景:App Blocks App Blocks:Shopifyテーマにアプリ独自の要素を埋め込める仕組み 図の引用元: https://www.shopify.com/in/partners/blog/theme-app-extensions 10
課題の背景:App Blocksのつくりかた {% assign point = customer.metafields.point.value | at_least: 0 %} {{% if point > 0 %}} <p> {{ point | money_without_currency }}ポイント </p> {{% endif %}} 11
課題の背景: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
課題の背景: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
課題:LiquidとJavaScriptの併用 テンプレートやロジックがLiquidとJavaScriptに分散して 見通しが悪く、つらい querySelector, textContentがJavaScriptのコードの大半を占め まるでDOMパズル どことなくjQueryの時代を彷彿とさせる... 14
課題:LiquidとJavaScriptの併用 テンプレートやロジックがLiquidとJavaScriptに分散して 見通しが悪く、つらい querySelector, textContentがJavaScriptのコードの大半を占め まるでDOMパズル どことなくjQueryの時代を彷彿とさせる... 💡全てをJavaScriptに寄せればいいのでは?💡 15
解決策の模索:App blocksの制約 全てをJavaScriptに寄せるライブラリたち 代表例 16
解決策の模索:App blocksの制約 全てをJavaScriptに寄せるライブラリたちは使い難い 17
解決策の模索: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
たどり着いた解 ライブラリなしでJavaScriptでコンポーネントをつくる方法 💪💪💪Web Components💪💪💪 19
たどり着いた解 方針 ● ● Liquidはカスタム要素に属性値を渡すだけに徹する テンプレートとかロジックとかもろもろはJavaScriptでやる 20
具体的なコード例:カスタム要素を定義する
Liquid
<dokuji-element
point="{{
customer.metafields.point.value }}"
></dokuji-element>
JavaScript
class DokujiElement extends
HTMLElement {
// ...
connectedCallback() {
// さまざまな処理
this.innerHTML = `
<p>HTML文字列</p>
`
}
}
21
具体的なコード例: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
具体的なコード例:状態管理してみたり
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
良くなったところ ● テンプレートエンジンをJavaScript側に一元化 ● ライフサイクルが提供されている ● JavaScriptの豊富な表現力を存分に使える ☺ うれしい ☺ 24
むずいところ ● 定型的な記述量が多い ○ ● 足りないところを自前で書く必要がある ○ ● ライフサイクルメソッドとか 状態管理とか インタラクティビティ ○ ReactみたいにonClickでシュッと書けない 25
むずいところ ● 定型的な記述量が多い ○ ● 足りないところを自前で書く必要がある ○ ● ライフサイクルメソッドとか 状態管理とか インタラクティビティ ○ ReactみたいにonClickでシュッと書けない ⚠ Web Componentsを生で使うときの話 ⚠ 26
今回の流れまとめ 1. App Blocksで動的なブロックをつくろうとすると LiquidとJavaScriptがつらい 2. Liquidを薄くしてJavaScriptに寄せよう 3. ライブラリは使えないよ 4. Web Componentsを使うと便利 27
伝えたいこと ● ShopifyのApp Blocks開発にWeb Componentsが便利! ● 弱点を知りつつみんなも使おう! 28
良いWeb Componentsライフを!