Vue 3 で Shopify 埋め込みアプリを作る
埋め込みアプリ(Shopify はその UI を App Home と呼びます)は、自分でホストする web アプリで、Shopify が管理画面内の iframe に表示します。Vue 3 を含め、どの web フレームワークでも作れます。
runtime では、Shopify の CDN から次の 2 つが提供されます。
- App Bridge(
app-bridge.js)は iframe を管理画面と接続し、グローバルなshopifyオブジェクト(トースト、モーダル、ナビゲーション、セッショントークンなど)を公開します。 - Polaris web components(
polaris-2.js)は<s-*>カスタム要素を登録します。
polaris-vue-elements は、それらの要素の上に型付きの Vue 3 コンポーネント(SPage、STextField、SModal など)を 追加します。これはコミュニティプロジェクトであり、Shopify が作ったものではありません。
Shopify 自身のドキュメントやテンプレートは React を対象としているため、このガイドでは Vue 固有の 部分を扱います。はじめにを読んでいることを前提としています。
アーキテクチャ
- フロントエンド: 自分で管理する URL から配信される Vue 3 + Vite のシングルページアプリ。 Shopify はこれを管理画面の iframe 内に読み込みます。
- バックエンド: Shopify の ID トークンを検証し、Admin API を呼び出せるサーバーであれば何でも構いません。 認証には トークン交換 を使います。フロントエンドが ID トークンを送信し、バックエンドがそれを検証してアクセストークンと 交換します。
shopify app init は React Router アプリを生成します。Shopify は Vue やその他の React 以外のテンプレートを提供していません。他のスタックについては、Shopify は認証のドキュメントと 各言語の公式 API ライブラリ(フレームワークに依存しない Node.js 版もあります)を案内しています。 実用的な構成の一つは、バックエンドを好きな方法で生成し、Vue アプリは以下の説明に従って 別途ビルドすることです。
shopify.app.toml では、embedded = true でアプリを埋め込みにし、application_url で アプリの配信先を指定します。
1. App Bridge と Polaris を読み込む
index.html の <head> に、shopify-api-key meta タグ(アプリのクライアント ID)と 両方のスクリプトを追加します。
<!-- index.html -->
<!doctype html>
<html>
<head>
<meta name="shopify-api-key" content="%VITE_SHOPIFY_API_KEY%" />
<script src="https://cdn.shopify.com/shopifycloud/app-bridge.js"></script>
<script src="https://cdn.shopify.com/shopifycloud/polaris-2.js"></script>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>Vite は index.html 内の %VITE_*% 変数を環境の値(たとえば .env の VITE_SHOPIFY_API_KEY)で置き換えます。キーをサーバー側から埋め込むこともできます。
App Bridge は Polaris とは別にバージョン管理されています。URL にバージョンはなく、常に最新の App Bridge が読み込まれます。Polaris のチャンネル(polaris-2.js、固定リリース)については 既知の注意点を参照してください。
アプリは Shopify 管理画面から開いてください(たとえば shopify app dev が出力するプレビュー URL)。 ブラウザのタブで直接開くと、周囲に管理画面がないため App Bridge の機能は動作しません。
shopify グローバルに型付きでアクセスするには、Shopify の型パッケージをインストールして 参照します(Shopify はこの用途に @shopify/app-bridge-types を案内しています)。
npm install -D @shopify/app-bridge-types// env.d.ts
/// <reference types="@shopify/app-bridge-types" />2. Vite を設定する
s-* タグをカスタム要素として扱います。はじめに と同じ設定です。
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: (tag) => tag.startsWith('s-'),
},
},
}),
],
})3. ページを作る
このページには、v-model を使ったフォーム、バックエンドを呼び出す保存ボタン、リセットを 確認するモーダルがあります。SModal は commandFor/command で宣言的に開きます。 SModal と modal のアクション slot に関する注意点も参照してください。
<script setup lang="ts">
import { ref } from 'vue'
import { SButton, SCheckbox, SModal, SPage, SSection, SStack, SText, STextField } from 'polaris-vue-elements'
const title = ref('')
const enabled = ref(true)
const saving = ref(false)
async function save(): Promise<void> {
saving.value = true
try {
const res = await fetch('/api/settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: title.value, enabled: enabled.value }),
})
if (!res.ok) throw new Error(`Request failed: ${res.status}`)
shopify.toast.show('Settings saved')
} catch {
shopify.toast.show('Could not save settings', { isError: true })
} finally {
saving.value = false
}
}
function reset(): void {
title.value = ''
enabled.value = true
}
</script>
<template>
<SPage heading="Settings">
<SSection heading="Widget">
<SStack gap="base">
<STextField v-model="title" label="Title" placeholder="Welcome" />
<SCheckbox v-model="enabled" label="Enabled" />
<SStack direction="inline" gap="base">
<SButton variant="primary" :loading="saving" @click="save">Save</SButton>
<SButton commandFor="reset-modal" command="--show">Reset</SButton>
</SStack>
</SStack>
</SSection>
<SModal id="reset-modal" heading="Reset settings?">
<SText>This restores the default values.</SText>
<template #primary-action>
<SButton variant="primary" commandFor="reset-modal" command="--hide" @click="reset">Reset</SButton>
</template>
<template #secondary-actions>
<SButton variant="secondary" commandFor="reset-modal" command="--hide">Cancel</SButton>
</template>
</SModal>
</SPage>
</template>4. バックエンドと通信する
通常、トークンを自分で付与する必要はありません。App Bridge は fetch() をインターセプトし、 アプリのドメインとそのサブドメインへのリクエストに対して、Authorization ヘッダーに ID トークンを (さらに Accept-Language にショップのロケールを)追加します。通常の相対パスでの呼び出しで動作します。
const response = await fetch('/api/products')
const data = await response.json()サーバー側では次のことを行います。
- すべてのリクエストで ID トークンを検証します。クライアントシークレットによる HS256 署名、
expとnbf、audがクライアント ID と一致すること、issとdestのホスト名が一致することを確認し、 失敗した場合は401を返します。 - ID トークンの有効期限は 1 分で、ベアラー認証情報です。自分のバックエンドにだけ送信してください。
- Admin API を呼び出すために ID トークンをアクセストークンと交換し、そのトークンはサーバー上で 保持してください。
shopify.idToken() を自分で呼び出すのは、WebSocket 接続のようにインターセプターが対象としない リクエストの場合だけです。
const token = await shopify.idToken()
const socket = new WebSocket(`wss://your-app.example.com/ws?token=${token}`)アプリが GraphQL Admin API だけを必要とする場合、アプリの設定で Direct API access を有効にすると、 App Bridge が fetch('shopify:admin/api/graphql.json', ...) を管理画面経由でルーティングできるため、 独自のバックエンドは不要です。 Resource fetching を参照してください。
5. Vue から App Bridge API を使う
すべて shopify グローバル上にあり、Vue プラグインは不要です。
トースト
shopify.toast.show('Product saved', {
duration: 5000,
action: 'Undo',
onAction: () => undoSave(),
})show(message, options) は duration(ミリ秒)、isError、action、onAction、 onDismiss を受け取り、shopify.toast.hide(id) に渡せる id を返します。
id によるモーダル操作
commandFor のほかに、shopify.modal でも id を指定して SModal を制御できます。
await shopify.modal.show('reset-modal')
await shopify.modal.hide('reset-modal')アプリのナビゲーション
<s-app-nav> は管理画面のサイドバーにアプリのナビゲーションを表示します。アプリのルート付近、 たとえば App.vue でマウントしたままにしてください。
<template>
<s-app-nav>
<s-link href="/">Home</s-link>
<s-link href="/settings">Settings</s-link>
</s-app-nav>
<RouterView />
</template>アクティブな項目は現在の URL から決まります。コードからナビゲートする方法として、Shopify の Navigation API はアンカー、open('/settings', '_self')、History API(history.pushState)を説明しています。
保存バー
<form> に data-save-bar を追加すると、フォームに未保存の変更があるときに管理画面が保存バーを 表示します。保存(Save)で submit が、破棄(Discard)で reset が発火します。同じフォームで プログラムによる shopify.saveBar メソッドと併用しないでください。 Save bar を参照してください。
次のステップ
- すべてのコンポーネントを見て、既知の注意点を読んでください。
v-model、オーバーレイ、生のタグへの型付けについてははじめにを読み返してください。- Shopify の App Home ドキュメントと App Bridge API を読んでください。