Shopify Theme App Extension: ফোল্ডার স্ট্রাকচার আর কোড কীভাবে স্টোরে পৌঁছায়
Shopify Theme App Extension: ফোল্ডার স্ট্রাকচার আর কোড কীভাবে স্টোরে পৌঁছায়
আপনি যদি Next.js, plain JS বা সাধারণ কোনো build step-এর মতো আধুনিক ওয়েব স্ট্যাক থেকে আসেন, তাহলে প্রথমে Shopify-র অ্যাপ মডেলটা একটু অদ্ভুত লাগতে পারে। এর বেশিরভাগটা বুঝে ফেলার আসল চাবিকাঠি একটাই কনসেপ্ট — theme app extension: merchant-এর storefront-এ নিজের UI ঢোকানোর অফিশিয়াল উপায়, তাও আবার merchant-এর theme file-এ কোনো হাত না দিয়েই।
আমি Store Essentials নামে ছোট, ফ্রি একটা অ্যাপ বানাচ্ছি, যেটা তিনটা storefront block দেয় — একটা trust/payment-badge, একটা announcement bar, আর একটা FAQ accordion। এই পোস্টে আমি লিখছি পুরো জিনিসটা ভেতরে কীভাবে জোড়া লাগানো: ফোল্ডার স্ট্রাকচার, একটা block-এর গঠন, আর যে pipeline আপনার কোডকে একটা live product page-এ পৌঁছে দেয়।
Shopify-র আগের কোনো জ্ঞান ধরে নিচ্ছি না। HTML আর JSON পড়তে পারলেই আপনি সাথে সাথে চলতে পারবেন।
১. মূল ধারণা: merchant-এর theme-এ হাত দেবেন না
Theme app extension আসার আগে অ্যাপগুলো merchant-এর theme file (theme.liquid, section file ইত্যাদি) সরাসরি বদলে ফিচার ঢোকাতো। এটা ছিল নাজুক: একটা অ্যাপ theme ভেঙে ফেলতে পারত, আর uninstall করলে পেছনে "orphaned" কোড থেকে যেত।
Theme app extension এই ব্যাপারটাই উল্টে দেয়। আপনার অ্যাপ একটা self-contained block-এর বান্ডল দেয়। Merchant সেগুলো theme editor দিয়ে টেনে বসায়, আর Shopify সেগুলো render করে। অ্যাপ uninstall করলেই block-গুলো পরিষ্কারভাবে উধাও। আপনি কখনো merchant-এর theme file পরিবর্তন করেন না।
আপনি যা ship করতে পারেন তার দুই ধরন আছে, আর এদের পার্থক্য মাত্র এক লাইন config-এ:
ধরন | এটা কী | কোথায় বসে | Merchant কীভাবে চালু করে |
|---|---|---|---|
App block | একটা দৃশ্যমান component, যেটা merchant বসায় | কোনো section-এর ভেতরে (যেমন product page) | Theme editor → Add block → Apps |
App embed | সাইট-ব্যাপী injection |
| Theme editor → Theme settings → App embeds (একটা toggle) |
প্রতিটা পেজে দেখা যায় এমন announcement bar হলো app embed। "Add to cart"-এর নিচে বসানো trust badge হলো app block। একই extension, শুধু target আলাদা — একটু পরেই দেখব এটা ঠিক কোথায় ঘোষণা করা হয়।
Shopify টার্ম: "Online Store 2.0" (OS 2.0)। App block শুধু আধুনিক theme-এ (Dawn, Horizon ইত্যাদি) কাজ করে, যারা JSON template ব্যবহার করে আর "sections everywhere" সাপোর্ট করে। App embed যেকোনো theme-এ কাজ করে। এই কারণেই dev store-এ ডিফল্ট theme হিসেবে Dawn থাকে।
২. ফোল্ডার স্ট্রাকচার
আমার প্রজেক্টটা একটা pnpm monorepo, তাই Shopify অ্যাপটা apps/-এর নিচে থাকে। এই পোস্টের জন্য যেটা জরুরি, সেটা হলো extension ফোল্ডারটা নিজে:
BanglaTechBlocks/ # monorepo root (pnpm workspace, git)
└── apps/
└── store-essentials/ # Shopify অ্যাপ
├── shopify.app.toml # অ্যাপ-লেভেল config (name, scopes, ...)
└── extensions/
└── store-essentials-blocks/ # ← theme app extension
├── shopify.extension.toml # extension manifest
├── blocks/ # প্রতি block-এর জন্য একটা .liquid ফাইল
│ └── star_rating.liquid
├── snippets/ # reusable Liquid partial
│ └── stars.liquid
├── assets/ # CSS, JS, ছবি (SVG ইত্যাদি)
│ └── thumbs-up.png
└── locales/ # অনুবাদযোগ্য টেক্সট
└── en.default.json
প্রতিটা অংশ কী করে:
shopify.extension.toml— এই manifest Shopify-কে জানায় "এই ফোল্ডারটা একটা theme app extension।" এটা খুবই ছোট:name = "store-essentials-blocks" type = "theme" uid = "273f5584-..." type = "theme"লাইনটাই এটাকে theme app extension হিসেবে চিহ্নিত করে।uidহলো একটা স্থায়ী identifier, যেটা দিয়ে Shopify বিভিন্ন version জুড়ে এই extension-কে ট্র্যাক করে।blocks/— এটাই মূল কেন্দ্র। এখানে প্রতিটা.liquidফাইল একটা করে block, যেটা merchant যোগ করতে পারে। একটাtrust-badge.liquidযোগ করলেই আপনি একটা block যোগ করে ফেললেন। আপনার বেশিরভাগ কাজ এখানেই।snippets/— reusable Liquid partial, ঠিক theme snippet-এর মতোই।{% render 'stars' %}দিয়ে টেনে আনেন। block ফাইল পরিষ্কার রাখতে দারুণ কাজে দেয়।assets/— static ফাইল: CSS, JS আর ছবি। Best practice হলো সবকিছু local রাখা (inline SVG, bundled CSS), কোনো CDN-এ hotlink না করা — এতে দ্রুত হয়, offline-ও কাজ করে, আর external-request নিয়ে app review-এর ঝামেলা এড়ানো যায়।locales/— অনুবাদের টেক্সট।en.default.jsonহলো ডিফল্ট;fr.json,es.jsonইত্যাদি যোগ করলে Shopify সঠিকটা serve করে। Liquid-এ একটা key ব্যবহার করেন{{ 'ratings.stars.label' | t }}দিয়ে (tfilter মানে "translate")।
৩. একটা block-এর গঠন (anatomy)
একটা block ফাইল আসলে দুইটা জিনিস একসাথে জোড়া: Liquid markup (যে HTML render হয়) আর একটা {% schema %} (একটা JSON block, যেটা block-এর setting আর কোথায় এটা থাকতে পারে তা ঠিক করে)। নিচে Shopify-র CLI যে example block তৈরি করে সেটা দেখুন, একটা star-rating widget:
{% assign avg_rating = block.settings.product.metafields.demo.avg_rating.value | round %}
<span style="color:{{ block.settings.colour }}">
{% render 'stars', rating: avg_rating %}
</span>
{% schema %}
{
"name": "Star Rating",
"target": "section",
"settings": [
{ "type": "product", "id": "product", "label": "product", "autofill": true },
{ "type": "color", "id": "colour", "label": "Star Colour", "default": "#ff0000" }
]
}
{% endschema %}
এখানে তিনটা জিনিস বোঝা দরকার:
target — এটাই সবচেয়ে গুরুত্বপূর্ণ ফিল্ড, কারণ এটা ঠিক করে আপনার block আসলে কোন ধরনের জিনিস:
"target": "section"→ একটা app block। যেসব section app block গ্রহণ করে, তাদের ভেতরে Add block → Apps-এ এটা দেখা যায়।"target": "head"বা"target": "body"→ একটা app embed। এটা সাইট-ব্যাপী<head>বা<body>-র শেষে inject হয়, আর merchant Theme settings থেকে toggle করে।
এই একটা ফিল্ডই "product page-এ বসানো একটা badge" আর "প্রতি পেজে একটা announcement bar"-এর পুরো পার্থক্য।
settings — merchant যেসব control এডিট করতে পারবে তার একটা array। প্রতিটা setting-এর একটা type থাকে (text, color, checkbox, range, product, select, …), একটা id, আর একটা label। Merchant যা দেয়, সেটা Liquid-এ block.settings.<id> হিসেবে পাওয়া যায়। তাই উপরের colour setting-টা পড়া হয় {{ block.settings.colour }} দিয়ে। এটাই Shopify-র "props"-এর সংস্করণ — একটা schema-চালিত settings panel, যেটা Shopify আপনার হয়ে render করে দেয়, কোনো React লাগে না।
Liquid body — সাধারণ server-rendered templating। {% assign %} variable বানায়, {{ ... }} সেটা print করে, {% render 'snippet' %} একটা partial include করে, আর | round-এর মতো filter মান পরিবর্তন করে। এটা Shopify-র server-এ page render হওয়ার সময় চলে।
presetsনিয়ে একটা নোট: theme section-এর schema-তে আর কিছু টিউটোরিয়ালে আপনিpresetsদেখবেন। কিন্তু theme app extension-এর app block-এর ক্ষেত্রে, editor-এ block-টা যে ফিল্ড দিয়ে আসলে register হয় সেটাtarget।targetঠিক থাকলে block-টা Apps-এর নিচে দেখা যাবে; ভুল হলে (বা host section যে target সাপোর্ট করে না) block-টা চুপচাপ দেখা যাবে না। এই "চুপচাপ না-দেখানো" জিনিসটাই নতুনদের সবচেয়ে সাধারণ ফাঁদ।
৪. আপনার কোড কীভাবে product page-এ পৌঁছায়: dev pipeline
এই অংশটাই আমার কাছে জাদুর মতো লাগছিল, যতক্ষণ না ট্রেস করলাম। অ্যাপ থেকে আপনি একটা কমান্ড চালান:
pnpm dev # → shopify app dev
…আর আসলে ভেতরে যা ঘটে, ধাপে ধাপে:
আপনার ল্যাপটপ Shopify dev store
────────────── ─────── ─────────
shopify app dev
│
├─ theme check ───────────────▶ (আপনার Liquid lint করে)
│
├─ blocks/ snippets/ assets/ bundle করে
│
├─ *development version* হিসেবে upload ─────▶ আপনার অ্যাপের সাথে register
│
├─ অ্যাপ install করে ───────────────────────────────────────▶ অ্যাপ installed
│
└─ local proxy চালু করে (127.0.0.1:9293) ◀── live preview + hot reload
আমার নিজের রান করা আসল output একই গল্প বলে:
The theme app extension development server is ready.
1. Install your app in your development store
2. Setup your theme app extension in the host theme
3. Preview your theme app extension at http://127.0.0.1:9293
store-essentials-blocks │ Running theme check on your Theme app extension...
store-essentials-blocks │ Bundling theme extension store-essentials-blocks...
app-preview │ ✅ Ready, watching for changes in your app
app_access │ App has been installed
এটা ভেঙে দেখি:
Theme check কিছু ship হওয়ার আগেই আপনার Liquid-এ সাধারণ ভুলগুলো lint করে ধরে।
Bundling আপনার
blocks/,snippets/,assets/আরlocales/-কে একটা extension bundle-এ প্যাকেজ করে।Development version হিসেবে upload। আপনার extension একটা ক্ষণস্থায়ী (ephemeral) dev version হিসেবে Shopify-তে push হয়, যেটা আপনার CLI session-এর সাথে যুক্ত — এটা public release না, শুধু টেস্টের জন্য live-editable একটা snapshot।
App install। CLI আপনার অ্যাপকে আপনার development store-এ install করে, আর এটাই আপনার block-গুলোকে সেই store-এর theme editor-এ available করে তোলে।
Local proxy + hot reload।
127.0.0.1:9293-এর server asset request proxy করে, ফলে আপনি একটা.liquidফাইল এডিট করলেই theme editor preview-তে প্রায় সাথে সাথেই পরিবর্তন দেখা যায় — কোনো redeploy লাগে না।
block-টা আসলে বসানো
shopify app dev চালু থাকলে availability-র কাজ শেষ — কিন্তু block-টা এখনো বসাতে হবে। সেটা হয় theme editor-এ:
App block-এর জন্য: theme editor খুলুন, এমন একটা template-এ যান যার একটা section app block সাপোর্ট করে (যেমন product page-এর "Product information" section), Add block-এ ক্লিক করুন, Apps ট্যাবে যান, আর আপনার block বেছে নিন। এরপর ডান পাশের panel-এ এর setting এডিট করুন — এগুলোই আপনার schema-তে দেওয়া
settings।App embed-এর জন্য: Theme settings → App embeds খুলুন আর toggle অন করুন। সাথে সাথেই সাইট-ব্যাপী live।
এখানে মূল মানসিক মডেলটা এই: shopify app dev block-টাকে available করে; merchant (বা আপনি) সেটাকে visible করে। আপনার অ্যাপ কখনো ঠিক করে না block-টা পেজের কোথায় বসবে — merchant সেটা ঠিক করে, বসিয়ে দিয়ে। এটাই এই মডেলের পুরো উদ্দেশ্য।
একটা section-কে কেন app block "গ্রহণ" করতে হয়? OS 2.0 theme-এ, একটা section-এর নিজের schema
"@app"type-এর block allow করে আর Liquid-এ সেগুলোর উপর loop চালিয়ে opt-in করে। Dawn আর Horizon তাদের product section-এ এটা আগে থেকেই করে রাখে, তাই আপনার app block সেখানে "এমনিতেই কাজ করে।" এমন কোনো section নেই এমন legacy theme-এ app block-এর বসার জায়গা নেই — আর ঠিক এই কারণেই app block-এর জন্য OS 2.0 লাগে।
৫. Dev বনাম Deploy: দুই রকম push
এই দুটোকে গুলিয়ে ফেলা সহজ, কিন্তু এরা আলাদা:
|
| |
|---|---|---|
উদ্দেশ্য | live local development | আসল release ship করা |
কী তৈরি করে | একটা ephemeral development version | একটা স্থায়ী app version |
কে দেখে | শুধু আপনার dev store, আপনার session-এ | যে merchant-ই অ্যাপ install করে |
আয়ু | process বন্ধ করলেই শেষ | স্থায়ী; এটাই released snapshot |
Hot reload | হ্যাঁ | না — এটা একটা build artifact |
তাই প্রতিদিনের কাজের loop-টা এমন: shopify app dev চালান, block এডিট করুন, editor-এ সেগুলো hot-reload হতে দেখুন। কোনো ধাপ সত্যিই শেষ ও verified হলে, shopify app deploy চালিয়ে একটা version কাটেন। Merchant শুধু আপনি যা deploy করেছেন তা-ই পায়।
৬. এই আর্কিটেকচার কেন বোঝা দরকার
Next.js থেকে এসে আমার কাছে যে জিনিসটা শেষমেশ পরিষ্কার হলো: একটা theme app extension আসলে একটা plugin contract-এর মতো। আপনি declarative component ship করেন (Liquid + একটা settings schema); host (merchant-এর theme) ঠিক করে সেগুলো কোথায় mount হবে; আর platform (Shopify) render, settings UI, translation আর lifecycle সামলায়। আপনি কখনো পেজটার মালিক না — আপনি সেটাতে অবদান রাখেন।
এই সীমাবদ্ধতাটাই আবার একটা ফিচার। যেহেতু আপনি theme file-এ হাত দিতে পারেন না:
আপনার অ্যাপ uninstall করলে কোনো store ভাঙতে পারে না।
Merchant আপনার UI-র জন্য বিনামূল্যেই একটা native, drag-and-drop এডিটিং অভিজ্ঞতা পায়।
এমন pure-storefront block-এর জন্য আপনি সর্বনিম্ন বা শূন্য API scope চান — যেটা trust-এর জন্য ভালো, আর app review পাস করতেও সুবিধা।
৭. সারসংক্ষেপ
একটা theme app extension অ্যাপকে theme file এডিট না করেই storefront-এ UI ঢোকাতে দেয়।
এটা একটা ফোল্ডার:
blocks/(প্রতি block-এ একটা.liquid),snippets/,assets/,locales/, আর একটাshopify.extension.tomlmanifest।প্রতিটা block = Liquid markup + একটা
{% schema %}। schema-রtargetঠিক করে এটা app block (section) নাকি app embed (head/body), আরsettingsLiquid-এblock.settings.*হয়ে যায়।shopify app devআপনার extension bundle করে, একটা ephemeral dev version upload করে, dev store-এ অ্যাপ install করে, আর একটা live preview hot-reload করে। এটা block-টাকে available করে; আপনি theme editor-এ সেটা বসান।shopify app deployস্থায়ী সেই version কাটে, যেটা আসল merchant-রা পায়।
সিরিজের পরের পর্বে: প্রথম আসল block বানানো — একটা trust / payment-badge app block, inline SVG icon আর ঠিকঠাক একটা settings panel সহ। আমি schema-র ডিজাইন আর namespaced CSS নিয়ে হাঁটব, যেটা host theme-এর সাথে block-টাকে সংঘর্ষে যেতে দেয় না।
এই পোস্টটা Store Essentials-এর একটা build log-এর অংশ, একটা ছোট ওপেন Shopify অ্যাপ। প্রশ্ন বা সংশোধন স্বাগত।
Comments (0)
Login to leave a comment.