क्रोम वेब स्टोर अब केवल मैनिफ़ेस्ट वी३ एक्सटेंशन स्वीकार करता है। अगर आपके पिछले एक्सटेंशन में अभी भी स्थायी बैकग्राउंड पेज, browserAction, या ब्लॉकिंग webRequest था, तो वह मॉडल खत्म हो चुका है। जगह लेता है छोटा जीवन वाला सर्विस वर्कर, सख्त अनुमतियाँ, और पेज कोड व एक्सटेंशन कोड के बीच साफ़ बँटवारा।

यह लेख एक छोटा एक्सटेंशन शुरू से अंत तक बनाता है: टूलबार आइकन पर क्लिक, सक्रिय टैब पर शब्दों की गिनती, पेज पर टोस्ट, और आइकन बैज पर संख्या। कोई फ्रेमवर्क नहीं, कोई बिल्ड स्टेप नहीं। लगभग हर असली एक्सटेंशन में काम आने वाले हिस्से मिलते हैं: मैनिफ़ेस्ट, सर्विस वर्कर, कंटेंट स्क्रिप्ट, संदेश-विनिमय, और अनुमतियाँ


आप क्या बनाएँगे

वर्ड काउंट एमवी३ चार काम करता है:

१. मैनिफ़ेस्ट वी३ वाले manifest.json से खुद को घोषित करता है। २. टूलबार क्लिक सुनने वाला सर्विस वर्कर चलाता है। ३. मेल खाते पेजों पर पहले से इंजेक्ट कंटेंट स्क्रिप्ट से बात करता है। ४. शब्द-संख्या से ऐक्शन बैज अपडेट करता है।

इसे chrome://extensions में अनपैक्ड एक्सटेंशन की तरह लोड करें। आर्किटेक्चर सीखने के लिए इतना काफी है। वेब स्टोर पर भेजना इन्हीं फ़ाइलों के ऊपर पैकेजिंग और समीक्षा है।


प्रोजेक्ट संरचना

पहला एक्सटेंशन सीधा रखें। नेस्टेड मोनोरिपो बाद में।

word-count-mv3/
  manifest.json
  background.js
  content.js
  styles.css
  icons/
    icon16.png
    icon48.png
    icon128.png
फ़ाइल भूमिका
manifest.json क्रोम के साथ अनुबंध: संस्करण, स्क्रिप्ट, अनुमतियाँ, आइकन
background.js सर्विस वर्कर: इवेंट, बैज, समन्वय
content.js पेज की दुनिया में (आइसोलेटेड): डोम, टोस्ट, शब्द-गिनती
styles.css टोस्ट के लिए इंजेक्ट सीएसएस
icons/ टूलबार और प्रबंधन पेज के चित्र

बाद में popup.html जोड़ सकते हैं। वी३ में पॉपअप ठीक हैं; लंबे समय तक इवेंट संभालने के लिए वे सर्विस वर्कर का विकल्प नहीं हैं।


मैनिफ़ेस्ट वी३ का खाका

{
  "manifest_version": 3,
  "name": "Word Count MV3",
  "version": "1.0.0",
  "description": "Count words on the current page from the toolbar.",
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  },
  "action": {
    "default_title": "Count words on this page",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png"
    }
  },
  "background": {
    "service_worker": "background.js"
  },
  "content_scripts": [
    {
      "matches": ["http://*/*", "https://*/*"],
      "js": ["content.js"],
      "css": ["styles.css"],
      "run_at": "document_idle"
    }
  ],
  "permissions": ["activeTab", "scripting"],
  "host_permissions": []
}

कुछ फ़ील्ड जो अक्सर उलझाते हैं:

  • manifest_version: 3 ज़रूरी है। आधी माइग्रेशन नहीं होती।
  • background.service_worker एक फ़ाइल पथ है (या बंडल एंट्री)। "persistent": true नहीं। वी२ जैसा बैकग्राउंड स्क्रिप्ट ऐरे नहीं।
  • action browser_action / page_action की जगह लेता है। एक टूलबार एंट्री।
  • content_scripts इंस्टॉल पर स्थिर इंजेक्शन के लिए अभी भी चलते हैं। केवल यूज़र जेस्चर के बाद वैकल्पिक इंजेक्शन के लिए chrome.scripting और activeTab बेहतर हैं।
  • permissions बनाम host_permissions: एपीआई क्षमताएँ permissions में। साइट एक्सेस पैटर्न (https://api.example.com/*) host_permissions में।

इस नमूने में कंटेंट स्क्रिप्ट के ब्रॉड मैच हैं, ताकि क्लिक पर स्क्रिप्ट पहले से मौजूद रहे। सख्त उत्पाद स्थिर content_scripts हटाकर activeTab + scripting रख सकता है और केवल क्लिक पर इंजेक्ट कर सकता है। दोनों वैध वी३ पैटर्न हैं।


सर्विस वर्कर (background.js)

सर्विस वर्कर एक्सटेंशन का इवेंट हब है। इवेंट आने पर शुरू होता है, खाली पड़ने पर रुक सकता है। इसे हमेशा चालू नोड प्रोसेस न समझें।

// background.js
chrome.action.onClicked.addListener(async (tab) => {
  if (!tab.id) return;

  try {
    const response = await chrome.tabs.sendMessage(tab.id, {
      type: "COUNT_WORDS",
    });

    const count = response?.count ?? 0;
    const text = count > 999 ? "999+" : String(count);

    await chrome.action.setBadgeText({ tabId: tab.id, text });
    await chrome.action.setBadgeBackgroundColor({
      tabId: tab.id,
      color: "#2563eb",
    });
  } catch (err) {
    // Content script missing (chrome:// pages, Web Store, not injected yet)
    await chrome.action.setBadgeText({ tabId: tab.id, text: "!" });
    console.warn("Word count failed:", err);
  }
});

// Optional: clear badge when the user navigates the tab
chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {
  if (changeInfo.status === "loading") {
    chrome.action.setBadgeText({ tabId, text: "" });
  }
});

डिबग समय बचाने वाली बातें:

  • default_popup सेट हो तो chrome.action.onClicked नहीं चलता। उस जेस्चर के लिए पॉपअप और क्लिक हैंडलर एक-दूसरे को काटते हैं।
  • tabs.sendMessage वहाँ फेल होता है जहाँ कंटेंट स्क्रिप्ट कभी नहीं चली: chrome://, वेब स्टोर, पीडीएफ व्यूअर, कुछ एरर पेज। कैच करें।
  • सर्विस वर्कर में डोम नहीं। document, window, localStorage उपलब्ध नहीं। chrome.storage इस्तेमाल करें; ऑफस्क्रीन दस्तावेज़ केवल तभी जब सच में डोम एपीआई (ऑडियो, कैनवास) चाहिए।

अगर कंटेंट स्क्रिप्ट स्थिर रूप से रजिस्टर नहीं है, मांग पर इंजेक्ट करें:

await chrome.scripting.executeScript({
  target: { tabId: tab.id },
  files: ["content.js"],
});

इस रास्ते को scripting अनुमति चाहिए, और या तो activeTab (यूज़र जेस्चर के बाद) या मेल खाते host_permissions


कंटेंट स्क्रिप्ट (content.js)

कंटेंट स्क्रिप्ट पेज का डोम साझा करती हैं, लेकिन डिफ़ॉल्ट पर उसका जावास्क्रिप्ट वर्ल्ड नहीं। आपकी वेरिएबल साइट के रिएक्ट बंडल से नहीं टकरातीं, और साइट आपकी फ़ंक्शन नहीं बुला सकती जब तक आप जान-बूझकर पुल न बनाएँ।

// content.js
function countWords(root = document.body) {
  const text = root?.innerText || "";
  const parts = text.trim().split(/\s+/).filter(Boolean);
  return parts.length;
}

function showToast(message) {
  const existing = document.getElementById("wc-mv3-toast");
  if (existing) existing.remove();

  const el = document.createElement("div");
  el.id = "wc-mv3-toast";
  el.className = "wc-mv3-toast";
  el.textContent = message;
  document.documentElement.appendChild(el);

  setTimeout(() => el.remove(), 2500);
}

chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
  if (message?.type !== "COUNT_WORDS") return;

  const count = countWords();
  showToast(`Words on this page: ${count}`);
  sendResponse({ count });
  // Return false: response is sync. Use return true only for async sendResponse.
});
/* styles.css */
.wc-mv3-toast {
  position: fixed;
  z-index: 2147483647;
  right: 16px;
  bottom: 16px;
  max-width: min(360px, 90vw);
  padding: 12px 16px;
  border-radius: 10px;
  background: #0f172a;
  color: #f8fafc;
  font: 14px/1.4 system-ui, sans-serif;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  pointer-events: none;
}

आइसोलेशन की याद रखने लायक बातें:

  • innerText बनाम textContent: innerText दिखने वाले पाठ के करीब है। भारी ऐप में छिपा यूआई दोनों तरीकों को गंदा कर सकता है।
  • मेन वर्ल्ड बनाम आइसोलेटेड वर्ल्ड: पेज की अपनी फ़ंक्शन बुलाने के लिए मेन वर्ल्ड में स्क्रिप्ट और सावधानी से संदेश-विनिमय चाहिए। जब तक साइट के जेएस से जोड़ जरूरी न हो, आइसोलेटेड रहें।
  • सीएसएस जंग: साइटें आक्रामक ग्लोबल स्टाइल लगाती हैं। यूनिक आईडी/क्लास प्रीफ़िक्स और अच्छी स्पेसिफ़िसिटी रखें। बड़े यूआई के लिए शैडो डोम विकल्प है।

अनुमति मॉडल (समीक्षा यहीं देखती है)

जितनी कम शक्ति से काम चले, उतनी ही माँगें।

ज़रूरत प्राथमिकता
क्लिक के बाद मौजूदा टैब पर प्रतिक्रिया activeTab
टैब में फ़ाइल या फ़ंक्शन इंजेक्ट scripting
एक्सटेंशन सेटिंग पढ़ना/लिखना storage
हमेशा https://api.example.com एक्सेस host_permissions
साइड पैनल खोलना sidePanel
नेटवर्क नियम रोकना/बदलना declarativeNetRequest (+ नियम)

activeTab उस टैब पर अस्थायी पहुँच देता है जहाँ से यूज़र ने आपको बुलाया। "आइकन पर क्लिक करूँ तो कुछ करो" के लिए यही सही डिफ़ॉल्ट है। इंस्टॉल पर कम डरावने प्रॉम्प्ट दिखते हैं।

host_permissions साइट पर स्थायी पहुँच हैं। एपीआई पोल, रिस्पॉन्स रीराइट, या बिना जेस्चर हर विज़िट पर इंजेक्शन के लिए इस्तेमाल करें। क्रोम इंस्टॉल पर इन्हें ज़्यादा साफ़ दिखा सकता है।

optional_permissions / optional_host_permissions से बाद में chrome.permissions.request से और माँग सकते हैं। उन पावर फीचर के लिए अच्छा है जिन्हें ज्यादातर यूज़र कभी चालू नहीं करते।

नेटवर्क बदलाव कड़ी तरह डिक्लेरेटिव नेट रिक्वेस्ट की ओर चला गया है। सामान्य कंटेंट बदलाव के लिए ब्लॉकिंग webRequest श्रोता वी३ का रास्ता नहीं है। अगर पुराना एक्सटेंशन बड़े onBeforeRequest हैंडलर में हेडर बदलता था, तो लाइन-बाय-लाइन पोर्ट नहीं, रूलसेट का नया डिज़ाइन सोचें।


संदेश-विनिमय पैटर्न

तीन चैनल ज़्यादातर ऐप ढक लेते हैं:

१. chrome.runtime.sendMessage / onMessage एक्सटेंशन पेज, सर्विस वर्कर, और कंटेंट स्क्रिप्ट के बीच। २. chrome.tabs.sendMessage एक्सटेंशन कॉन्टेक्स्ट से किसी टैब की कंटेंट स्क्रिप्ट तक। ३. chrome.runtime.connect लंबे पोर्ट के लिए (स्ट्रीमिंग लॉग, बहु-चरण यूआई)।

व्यावहारिक नियम:

  • हर संदेश ऑब्जेक्ट पर साफ़ type फ़ील्ड।
  • काम करने से पहले संदेश का आकार जाँचें।
  • असिंक काम में sendResponse अटपटा होता है: या return true करके बाद में बुलाएँ, या आधुनिक क्रोमियम में एक्सटेंशन मैसेजिंग के लिए async श्रोता से प्रॉमिस लौटाएँ।
  • कंटेंट स्क्रिप्ट ज़्यादातर विशेषाधिकार प्राप्त एपीआई सीधे नहीं बुला सकतीं; सर्विस वर्कर वह काम करता है और डेटा लौटाता है।

वर्कर के पीछे स्टोरेज का उदाहरण:

// content -> worker
chrome.runtime.sendMessage({ type: "SAVE_COUNT", count: 42 });

// worker
chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
  if (msg.type === "SAVE_COUNT") {
    chrome.storage.local.set({ lastCount: msg.count }).then(() => {
      sendResponse({ ok: true });
    });
    return true; // keep channel open for async response
  }
});

अनपैक्ड लोड करें

१. chrome://extensions खोलें। २. डेवलपर मोड चालू करें। ३. अनपैक्ड लोड चुनें और word-count-mv3 फ़ोल्डर लें। ४. कोई सामान्य https:// पेज खोलें। ५. पहेली आइकन पर क्लिक करें, एक्सटेंशन पिन करें, फिर उस पर क्लिक करें। ६. टोस्ट और बैज जाँचें।

सर्विस वर्कर में हर बदलाव के बाद एक्सटेंशन कार्ड पर रीलोड दबाएँ। कंटेंट स्क्रिप्ट बदलाव अगले पूरे पेज लोड (या दोबारा इंजेक्शन) पर लगते हैं। पुराने वर्कर "ठीक किया फिर भी कुछ नहीं बदला" वाली शिकायत का आम स्रोत हैं।

लॉग देखें:

  • सर्विस वर्कर: chrome://extensions → आपका कार्ड → सर्विस वर्कर लिंक (वर्कर का डेवटूल्स)।
  • कंटेंट स्क्रिप्ट: पेज का सामान्य डेवटूल्स कंसोल (ज़रूरत हो तो कॉन्टेक्स्ट से फ़िल्टर करें)।

आम गलतियाँ

१. सर्विस वर्कर को हमेशा चालू समझना

टाइमर, खुले वेबसॉकेट, और इन-मेमोरी कैश वर्कर सस्पेंड होने पर मर जाते हैं। ज़रूरी स्थिति chrome.storage में रखें। लंबे setInterval की कड़ियों की जगह जगाने के लिए अलार्म (chrome.alarms) इस्तेमाल करें। सॉकेट चाहिए तो जगने पर दोबारा कनेक्ट का डिज़ाइन लिखें।

२. रिमोट कोड और सख्त सीएसपी

वी३ रिमोट स्क्रिप्ट चलाने पर रोक लगाता है, और एक्सटेंशन कॉन्टेक्स्ट में eval / new Function लगभग वर्जित हैं। कोड बंडल करें। लॉजिक पैकेज के अंदर रखें। एक्सटेंशन का सीएसपी सामान्य वेबसाइट से सख्त होता है।

३. ऐसे "matches" जो कभी नहीं चलते

file:// पेजों के लिए "allow_access_to_file_urls" (यूज़र टॉगल) और साफ़ file:///* मैच चाहिए। chrome:// और वेब स्टोर सीमा से बाहर हैं। आईफ्रेम इंजेक्शन के लिए सबफ़्रेम चाहिए हों तो all_frames: true

४. रनटाइम पर अनुमति अस्वीकृत

घोषित अनुमतियाँ और दी गई वैकल्पिक अनुमतियाँ एक नहीं हैं। activeTab केवल उसी टैब पर यूज़र जेस्चर के बाद लगता है। होस्ट अनुमति के बिना वर्कर से fetch('https://...') फेल होता है, भले कंटेंट स्क्रिप्ट उसी यूआरएल को और तरीकों से लोड कर पाए।

५. नेविगेशन के बाद टूटा संदेश-विनिमय

टैब बीच नेविगेशन में हो और संदेश भेज दें; प्राप्त करने वाली कंटेंट स्क्रिप्ट चली गई। tabs.onUpdated के पूरा होने का इंतज़ार करें, या इंजेक्ट के बाद एक बार फिर कोशिश करें।

६. बैज और ऐक्शन स्थिति का रिसाव

बैज टेक्स्ट लगाना आसान है, टैब के बीच पुराना छोड़ना भी आसान। जब संख्या पेज-विशिष्ट हो तो tabId स्कोप्ड बैज एपीआई चुनें।

७. "type": "module" के बिना मॉड्यूल वर्कर

अगर background.js में import लिखते हैं, तो सेट करें:

"background": {
  "service_worker": "background.js",
  "type": "module"
}

नहीं तो वर्कर शुरू नहीं होता और एक्सटेंशन "मृत" लगता है।

८. हर जगह XMLHttpRequest की उम्मीद

सर्विस वर्कर में fetch प्राथमिकता दें। पुराने स्निपेट अभी भी बैकग्राउंड पेज वाले एक्सएचआर पैटर्न दिखाते हैं।

९. डेमो के लिए बहुत चौड़ी होस्ट अनुमतियाँ

"<all_urls>" चलता है, और समीक्षा की समस्या भी सिखाता है। संकीर्ण शुरू करें। फीचर मजबूर करे तब बढ़ाएँ।

१०. आइकन भूल जाना

बिना आइकन शौकिया दिखता है और टूलबार में खोजना मुश्किल। कम से कम १६, ४८, और १२८ दें।


फीचर जोड़ने से पहले न्यूनतम जाँच सूची

  • manifest_version मान 3 है
  • बैकग्राउंड सर्विस वर्कर पथ है
  • अनुमतियाँ असली एपीआई उपयोग से मेल खाती हैं
  • कंटेंट स्क्रिप्ट मैच (या डायनेमिक इंजेक्ट) केवल इच्छित साइटें ढकते हैं
  • संदेशों का टाइप या वर्शन वाला आकार है
  • वर्कर कोड साफ़ रीलोड होता है; हमेशा-जीवित मेमोरी पर निर्भर नहीं
  • प्रतिबंधित पेजों के फेल पथ संभाले गए हैं
  • टूलबार के लिए आइकन और नाम सेट हैं

जब यह सूची उबाऊ लगे, तब पॉपअप, ऑप्शंस पेज, कॉन्टेक्स्ट मेनू (contextMenus), या साइड पैनल जोड़ें। रीढ़ वही रहती है: मैनिफ़ेस्ट घोषणा करता है, वर्कर समन्वय करता है, कंटेंट स्क्रिप्ट डोम छूती है, अनुमतियाँ न्यूनतम रहती हैं।


समापन

मैनिफ़ेस्ट वी३ नई जावास्क्रिप्ट सिंटैक्स से कम, जीवनचक्र और विशेषाधिकार से ज़्यादा जुड़ा है। सर्विस वर्कर सो जाएगा। कंटेंट स्क्रिप्ट आइसोलेटेड रहेंगी। अनुमतियाँ जान-बूझकर बँटी हैं। पहले manifest.json से ही इन सीमाओं के हिसाब से डिज़ाइन करें तो "एक्सटेंशन अचानक रुक जाता है" वाले ज़्यादातर बग आते ही नहीं।

ऊपर वाली संरचना कॉपी करें, अनपैक्ड लोड करें, जान-बूझकर संदेश का type तोड़ें, और वर्कर डेवटूल्स में फेल देखें। वह दस मिनट का लूप वी२ और वी३ की एक और अमूर्त तुलना से ज़्यादा सिखाता है।

डेमो से आगे बढ़ें तो क्रोम की मौजूदा एक्सटेंशन डॉक्स में service_worker रजिस्ट्रेशन, activeTab, और डिक्लेरेटिव नेट रिक्वेस्ट पढ़ें। एपीआई छोटे-छोटे बदलते हैं; ऊपर का आर्किटेक्चर वर्षों से क्रोम एक्सटेंशन का स्थिर आकार है, और २०२६ में भी उसी पर बनाना चाहिए।