TypeScript में `Shared Worker`
यह लेख TypeScript में Shared Worker
की व्याख्या करता है।
हम TypeScript कोड उदाहरणों के साथ विस्तार से बताएँगे कि Shared Workers कैसे काम करते हैं और उन्हें व्यवहार में कैसे उपयोग करें।
YouTube Video
TypeScript में Shared Worker
Shared Worker
एक ही ओरिजिन पर कई टैब्स, विंडो और iframes के बीच साझा किया जाने वाला एकल वर्कर प्रोसेस है। इसे उपयोग करके, आप कई ब्राउज़र टैब्स में साझा स्टेट और संसाधनों को संभाल सकते हैं।
उदाहरण के लिए, आप साझा WebSocket कनेक्शन, टैब्स के बीच समकालित कैश और कतार प्रोसेसिंग, तथा पारस्परिक बहिष्करण (mutual exclusion) को कुशलतापूर्वक लागू कर सकते हैं।
Dedicated Worker
के विपरीत, Shared Worker
onconnect
ईवेंट के माध्यम से कई MessagePort
प्राप्त करता है और कई क्लाइंट्स के साथ संचार को मल्टीप्लेक्स कर सकता है।
वे स्थितियाँ जहाँ आपको Shared Worker
चुनना चाहिए
निम्न मामलों में, Shared Worker
का उपयोग उपयुक्त है।
- जब आपको टैब्स के बीच साझा स्टेट या पारस्परिक बहिष्करण चाहिए
- जब आप एक ही WebSocket कनेक्शन या IndexedDB एक्सेस साझा करना चाहते हैं
- जब आपको सभी टैब्स को सूचित करना हो (ब्रॉडकास्ट)
- जब आप संसाधनों की बचत के लिए भारी प्रोसेसिंग को केंद्रीकृत करना चाहते हैं
इसके विपरीत, निम्न मामलों में अन्य तरीके अधिक उपयुक्त हैं।
- जब आपको कैश नियंत्रण या ऑफ़लाइन समर्थन की आवश्यकता हो, तो आप
Service Worker
का उपयोग कर सकते हैं। - ऐसे भारी प्रोसेसिंग के लिए जो एक ही टैब तक सीमित हो, आप
Dedicated Worker
का उपयोग कर सकते हैं।
Shared Worker
के कार्यान्वयन के चरण
यहाँ, हम TypeScript का उपयोग करते हुए निम्नलिखित को चरण-दर-चरण लागू करेंगे।
- टाइप-सुरक्षित संदेश प्रोटोकॉल
- Promise-आधारित अनुरोध/प्रतिक्रिया (RPC)
- सभी टैब्स को ब्रॉडकास्ट
- हार्टबीट और क्लाइंट क्लीनअप
पर्यावरण सेटअप
ऐसे प्रत्येक स्रोत फ़ाइल को कम्पाइल करने के लिए कॉन्फ़िगरेशन बनाएँ जो Shared Worker
का उपयोग करती है।
1{
2 "compilerOptions": {
3 "target": "ES2020",
4 "module": "ES2020",
5 "lib": ["ES2020", "dom", "WebWorker"],
6 "moduleResolution": "Bundler",
7 "strict": true,
8 "noEmitOnError": true,
9 "outDir": "out",
10 "skipLibCheck": true
11 },
12 "include": ["src"]
13}
tsconfig-src.json
में, DOM और Web Worker टाइप डेफिनिशन सक्षम करें ताकि कोड को सुरक्षित रूप से कम्पाइल किया जा सके।
संदेश प्रोटोकॉल परिभाषित करना
संचार की नींव एक टाइप्ड मैसेज कॉन्ट्रैक्ट है। इसे पहले से परिभाषित करने से आगे का संचार सुरक्षित और विस्तार योग्य हो जाता है।
1// worker-protocol.ts
2// Define discriminated unions for structured messages
3
4export type RequestAction =
5 | { type: 'ping' }
6 | { type: 'echo'; payload: string }
7 | { type: 'getTime' }
8 | { type: 'set'; key: string; value: unknown }
9 | { type: 'get'; key: string }
10 | { type: 'broadcast'; channel: string; payload: unknown }
11 | { type: 'lock.acquire'; key: string; timeoutMs?: number }
12 | { type: 'lock.release'; key: string };
RequestAction
एक विभेदित यूनियन (टैग्ड यूनियन) है, जो वर्कर को भेजे जाने वाले अनुरोधों के प्रकारों का प्रतिनिधित्व करता है औरping
,get
तथाbroadcast
जैसे ऑपरेशंस को परिभाषित करता है।
1export interface RequestMessage {
2 kind: 'request';
3 id: string;
4 from: string;
5 action: RequestAction;
6}
RequestMessage
क्लाइंट से वर्कर को भेजे जाने वाले अनुरोध संदेशों की संरचना परिभाषित करता है।
1export interface ResponseMessage {
2 kind: 'response';
3 id: string;
4 ok: boolean;
5 result?: unknown;
6 error?: string;
7}
ResponseMessage
वर्कर से क्लाइंट को लौटाए जाने वाले प्रतिक्रिया संदेशों की संरचना परिभाषित करता है।
1export interface BroadcastMessage {
2 kind: 'broadcast';
3 channel: string;
4 payload: unknown;
5 from: string;
6}
BroadcastMessage
उन प्रसारण संदेशों की संरचना परिभाषित करता है जिन्हें वर्कर अन्य क्लाइंट्स को भेजता है।
1export type WorkerInMessage =
2 | RequestMessage
3 | { kind: 'heartbeat'; from: string }
4 | { kind: 'bye'; from: string };
WorkerInMessage
एक टाइप है जो वर्कर को प्राप्त होने वाले सभी संदेशों का प्रतिनिधित्व करता है, जैसे अनुरोध, हार्टबीट, और डिस्कनेक्ट सूचनाएँ।
1export type WorkerOutMessage = ResponseMessage | BroadcastMessage;
WorkerOutMessage
एक टाइप है जो वर्कर द्वारा क्लाइंट को भेजे जाने वाले प्रतिक्रिया या प्रसारण संदेशों का प्रतिनिधित्व करता है।
1export const randomId = () => Math.random().toString(36).slice(2);
randomId
एक फ़ंक्शन है जो संदेश ID आदि के लिए उपयोग करने हेतु एक रैंडम अल्फ़ान्यूमेरिक स्ट्रिंग उत्पन्न करता है।
Shared Worker को लागू करना
shared-worker.ts
में, onconnect
ईवेंट के माध्यम से कनेक्ट होने वाले टैब्स को रजिस्टर करें और संदेशों को हैंडल करें।
1// shared-worker.ts
2/// <reference lib="webworker" />
- यह डायरेक्टिव TypeScript को Web Workers के लिए टाइप डेफिनिशन लोड करने का निर्देश देता है।
1import {
2 WorkerInMessage,
3 WorkerOutMessage,
4 RequestMessage,
5 ResponseMessage,
6} from './worker-protocol.js';
- वर्कर संचार के लिए उपयोग की जाने वाली टाइप डेफिनिशन आयात करता है।
1export default {};
2declare const self: SharedWorkerGlobalScope;
- स्पष्ट रूप से घोषित करता है कि
self
Shared Worker
का वैश्विक स्कोप है।
1type Client = {
2 id: string;
3 port: MessagePort;
4 lastBeat: number;
5};
Client
एक टाइप है जो प्रत्येक क्लाइंट का पहचानकर्ता, संचार पोर्ट और अंतिम हार्टबीट टाइमस्टैम्प का प्रतिनिधित्व करता है।
1const clients = new Map<string, Client>();
2const kv = new Map<string, unknown>();
3const locks = new Map<string, string>();
4const HEARTBEAT_TIMEOUT = 30_000;
- कनेक्टेड क्लाइंट्स की सूची, की-वैल्यू स्टोर, लॉक स्थिति, और टाइमआउट अवधि का प्रबंधन करता है।
1function send(port: MessagePort, msg: WorkerOutMessage) {
2 port.postMessage(msg);
3}
send
एक यूटिलिटी फ़ंक्शन है जो निर्दिष्ट पोर्ट पर संदेश भेजता है।
1function respond(req: RequestMessage, ok: boolean, result?: unknown, error?: string): ResponseMessage {
2 return { kind: 'response', id: req.id, ok, result, error };
3}
respond
किसी अनुरोध के लिए एक प्रतिक्रिया संदेश उत्पन्न करता है।
1function broadcast(from: string, channel: string, payload: unknown) {
2 for (const [id, c] of clients) {
3 send(c.port, { kind: 'broadcast', channel, payload, from });
4 }
5}
broadcast
निर्दिष्ट चैनल पर सभी क्लाइंट्स को एक संदेश भेजता है।
1function handleRequest(clientId: string, port: MessagePort, req: RequestMessage) {
handleRequest
अनुरोधों को उनके प्रकार के अनुसार प्रोसेस करता है और परिणाम क्लाइंट को लौटाता है।
1 const { action } = req;
2 try {
3 switch (action.type) {
4 case 'ping':
5 send(port, respond(req, true, 'pong'));
6 break;
7 case 'echo':
8 send(port, respond(req, true, action.payload));
9 break;
10 case 'getTime':
11 send(port, respond(req, true, new Date().toISOString()));
12 break;
13 case 'set':
14 kv.set(action.key, action.value);
15 send(port, respond(req, true, true));
16 break;
17 case 'get':
18 send(port, respond(req, true, kv.get(action.key)));
19 break;
20 case 'broadcast':
21 broadcast(clientId, action.channel, action.payload);
22 send(port, respond(req, true, true));
23 break;
- इस कोड में, प्राप्त अनुरोध के प्रकार के आधार पर, यह संदेशों का भेजना और प्राप्त करना, डेटा प्राप्त करना और सहेजना, तथा प्रसारण को संभालता है।
1 case 'lock.acquire': {
2 const owner = locks.get(action.key);
3 if (!owner) {
4 locks.set(action.key, clientId);
5 send(port, respond(req, true, { owner: clientId }));
6 } else if (owner === clientId) {
7 send(port, respond(req, true, { owner }));
8 } else {
9 const start = Date.now();
10 const tryWait = () => {
11 const current = locks.get(action.key);
12 if (!current) {
13 locks.set(action.key, clientId);
14 send(port, respond(req, true, { owner: clientId }));
15 } else if ((action.timeoutMs ?? 5000) < Date.now() - start) {
16 send(port, respond(req, false, undefined, 'lock-timeout'));
17 } else {
18 setTimeout(tryWait, 25);
19 }
20 };
21 tryWait();
22 }
23 break;
24 }
- यह कोड निर्दिष्ट कुंजी के लिए क्लाइंट द्वारा लॉक प्राप्त करने की प्रक्रिया को लागू करता है। यदि लॉक पहले से होल्ड नहीं है, तो इसे तुरंत अधिग्रहित कर लिया जाता है; यदि वही क्लाइंट इसे फिर से अनुरोध करता है, तो उस अनुरोध को भी सफल माना जाता है। यदि कोई अन्य क्लाइंट लॉक पहले से होल्ड किए हुए है, तो लॉक रिलीज़ होने तक यह हर 25 मिलीसेकंड में पुनः प्रयास करता है, और यदि निर्दिष्ट टाइमआउट (डिफ़ॉल्ट 5 सेकंड) पार हो जाए, तो यह एक त्रुटि के साथ प्रतिक्रिया देता है।
1 case 'lock.release':
2 if (locks.get(action.key) === clientId) {
3 locks.delete(action.key);
4 send(port, respond(req, true, true));
5 } else {
6 send(port, respond(req, false, undefined, 'not-owner'));
7 }
8 break;
9 default:
10 send(port, respond(req, false, undefined, 'unknown-action'));
11 }
12 } catch (e: any) {
13 send(port, respond(req, false, undefined, e?.message ?? 'error'));
14 }
15}
- यह कोड क्लाइंट द्वारा धारण किया गया लॉक रिलीज़ करता है, और यदि क्लाइंट के पास अनुमति नहीं है या क्रिया अज्ञात है, तो एक त्रुटि प्रतिक्रिया लौटाता है।
1function handleInMessage(c: Client, msg: WorkerInMessage) {
2 if (msg.kind === 'heartbeat') {
3 c.lastBeat = Date.now();
4 } else if (msg.kind === 'bye') {
5 cleanupClient(msg.from);
6 } else if (msg.kind === 'request') {
7 handleRequest(c.id, c.port, msg);
8 }
9}
handleInMessage
क्लाइंट्स से प्राप्त संदेशों को पार्स करता है और अनुरोध तथा हार्टबीट को संभालता है।
1function cleanupClient(clientId: string) {
2 for (const [key, owner] of locks) {
3 if (owner === clientId) locks.delete(key);
4 }
5 clients.delete(clientId);
6}
cleanupClient
रजिस्ट्री और लॉक स्थिति से डिस्कनेक्टेड क्लाइंट्स को हटाता है।
1setInterval(() => {
2 const now = Date.now();
3 for (const [id, c] of clients) {
4 if (now - c.lastBeat > HEARTBEAT_TIMEOUT) cleanupClient(id);
5 }
6}, 10_000);
setInterval
का उपयोग करके समय-समय पर सभी क्लाइंट्स के हार्टबीट की जाँच करता है और टाइमआउट हो चुके कनेक्शनों को साफ करता है।
1self.onconnect = (e: MessageEvent) => {
2 const port = (e.ports && e.ports[0]) as MessagePort;
3 const clientId = crypto.randomUUID?.() ?? Math.random().toString(36).slice(2);
4 const client: Client = { id: clientId, port, lastBeat: Date.now() };
5 clients.set(clientId, client);
6
7 port.addEventListener('message', (ev) => handleInMessage(client, ev.data as WorkerInMessage));
8 send(port, { kind: 'broadcast', channel: 'system', payload: { hello: true, clientId }, from: 'worker' });
9 port.start();
10};
-
onconnect
तब कॉल होता है जब कोई नया टैब या पेजShared Worker
से कनेक्ट होता है, जिससे क्लाइंट रजिस्टर होता है और संचार शुरू होता है। -
इस फ़ाइल में, अनेक ब्राउज़र टैब्स के बीच साझा स्टेट प्रबंधन और संचार सक्षम करने वाले
Shared Worker
के मौलिक तंत्र लागू किए गए हैं।
क्लाइंट रैपर (RPC)
इसके बाद, Promise-आधारित RPC क्लाइंट बनाएँ।
1// shared-worker-client.ts
2import {
3 RequestAction,
4 RequestMessage,
5 WorkerOutMessage,
6 randomId
7} from './worker-protocol.js';
- वर्कर संचार के लिए उपयोग की जाने वाली टाइप डेफिनिशन और यूटिलिटी फ़ंक्शन्स आयात करता है।
1export type BroadcastHandler = (msg: {
2 channel: string;
3 payload: unknown;
4 from: string
5}) => void;
- यहाँ हम उस कॉलबैक फ़ंक्शन का टाइप परिभाषित करते हैं जो प्रसारण संदेश प्राप्त होने पर चलता है।
1export class SharedWorkerClient {
SharedWorkerClient
एक क्लाइंट क्लास है जोShared Worker
से संचार करती है, अनुरोध भेजती है और प्रतिक्रियाएँ संभालती है।
1 private worker: SharedWorker;
2 private port: MessagePort;
3 private pending = new Map<string, {
4 resolve: (v: any) => void;
5 reject: (e: any) => void
6 }>();
- ये वेरिएबल्स वर्कर इंस्टेंस, वर्कर के साथ संचार पोर्ट, और उन अनुरोधों को ट्रैक करने वाला मैप हैं जो प्रतिक्रियाओं की प्रतीक्षा में हैं।
1 private clientId = randomId();
2 private heartbeatTimer?: number;
3 private onBroadcast?: BroadcastHandler;
- ये वेरिएबल्स क्लाइंट पहचानकर्ता, हार्टबीट भेजने के लिए टाइमर, और प्रसारण प्राप्ति हैंडलर को रखते हैं।
1 constructor(url: URL, name = 'app-shared', onBroadcast?: BroadcastHandler) {
2 this.worker = new SharedWorker(url, { name, type: 'module' as any });
3 this.port = this.worker.port;
4 this.onBroadcast = onBroadcast;
- कन्स्ट्रक्टर में, यह
Shared Worker
से कनेक्शन प्रारंभ करता है और संदेश लिस्नर्स तथा हार्टबीट भेजने की व्यवस्था करता है।
1 this.port.addEventListener('message', (ev) => {
2 const msg = ev.data as WorkerOutMessage;
3 if (msg.kind === 'response') {
4 const p = this.pending.get(msg.id);
5 if (p) {
6 this.pending.delete(msg.id);
7 msg.ok ? p.resolve(msg.result) : p.reject(new Error(msg.error || 'error'));
8 }
9 } else if (msg.kind === 'broadcast') {
10 this.onBroadcast?.(msg);
11 }
12 });
13 this.port.start();
- यहाँ यह वर्कर से संदेश प्राप्त करता है और प्रतिक्रियाओं या प्रसारणों को संभालता है।
1 this.heartbeatTimer = window.setInterval(() => {
2 this.port.postMessage({ kind: 'heartbeat', from: this.clientId });
3 }, 10_000);
- कनेक्शन को सक्रिय रखने के लिए समय-समय पर हार्टबीट संदेश भेजता है।
1 window.addEventListener('beforeunload', () => {
2 try {
3 this.port.postMessage({ kind: 'bye', from: this.clientId });
4 } catch {}
5 if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
6 });
7 }
- विंडो बंद होने से पहले वर्कर को डिस्कनेक्ट अधिसूचना भेजता है।
1 request<T = unknown>(action: RequestAction): Promise<T> {
2 const id = randomId();
3 return new Promise<T>((resolve, reject) => {
4 this.pending.set(id, { resolve, reject });
5 this.port.postMessage({ kind: 'request', id, from: this.clientId, action });
6 });
7 }
request
मेथड निर्दिष्ट क्रिया को वर्कर को भेजता है और परिणामPromise
के रूप में प्राप्त करता है।
1 ping() {
2 return this.request<string>({ type: 'ping' });
3 }
4 echo(payload: string) {
5 return this.request<string>({ type: 'echo', payload });
6 }
7 getTime() {
8 return this.request<string>({ type: 'getTime' });
9 }
- ये बुनियादी संचार परीक्षण और वर्तमान समय प्राप्त करने के लिए यूटिलिटी मेथड्स हैं।
1 set(key: string, value: unknown) {
2 return this.request<boolean>({ type: 'set', key, value });
3 }
4 get<T = unknown>(key: string) {
5 return this.request<T>({ type: 'get', key });
6 }
- ये की-वैल्यू युग्मों को सहेजने और पुनः प्राप्त करने के लिए मेथड्स हैं।
1 broadcast(channel: string, payload: unknown) {
2 return this.request<boolean>({ type: 'broadcast', channel, payload });
3 }
- यह एक मेथड है जो वर्कर के माध्यम से अन्य क्लाइंट्स को प्रसारण संदेश भेजता है।
1 lockAcquire(key: string, timeoutMs?: number) {
2 return this.request<{ owner: string }>({ type: 'lock.acquire', key, timeoutMs });
3 }
4 lockRelease(key: string) {
5 return this.request<boolean>({ type: 'lock.release', key });
6 }
7}
- ये मेथड्स साझा संसाधनों पर पारस्परिक बहिष्करण प्राप्त करने हेतु लॉक प्राप्त करने और रिलीज़ करने का काम करते हैं।
- इस फ़ाइल में, प्रत्येक ब्राउज़र टैब से
Shared Worker
तक सुरक्षित, असिंक्रोनस संचार के लिए एक क्लाइंट API लागू किया गया है।
उदाहरण उपयोग
demo.ts
में, हम पहले बनाई गई SharedWorkerClient
क्लास का उपयोग करते हैं और उसके व्यवहार को सत्यापित करते हैं। यह क्रमवार रूप से संचार परीक्षण, डेटा पढ़ना-लिखना, प्रसारण, और लॉक प्रबंधन सहित फ़ंक्शनों की एक श्रृंखला निष्पादित करता है।
1// demo.ts
2import { SharedWorkerClient } from './shared-worker-client.js';
3
4const client = new SharedWorkerClient(new URL('./shared-worker.js', import.meta.url), 'app-shared', (b) => {
5 console.log('[BROADCAST]', b.channel, JSON.stringify(b.payload));
6});
7
8async function demo() {
9 console.log('ping:', await client.ping());
10 console.log('echo:', await client.echo('hello'));
11 console.log('time:', await client.getTime());
12
13 // Counter update
14 await client.set('counter', (await client.get<number>('counter')) ?? 0);
15 const c1 = await client.get<number>('counter');
16 await client.set('counter', (c1 ?? 0) + 1);
17 console.log('counter:', await client.get<number>('counter'));
18
19 // Broadcast test
20 await client.broadcast('notify', { msg: 'Counter updated' });
21
22 // Lock test
23 const key = 'critical';
24 console.log(`[lock] Trying to acquire lock: "${key}"`);
25 const lockResult = await client.lockAcquire(key, 2000);
26 console.log(`[lock] Lock acquired for key "${key}":`, lockResult);
27
28 try {
29 console.log(`[lock] Simulating critical section for key "${key}"...`);
30 await new Promise((r) => setTimeout(r, 250));
31 console.log(`[lock] Critical section completed for key "${key}"`);
32 } finally {
33 console.log(`[lock] Releasing lock: "${key}"`);
34 const releaseResult = await client.lockRelease(key);
35 console.log(`[lock] Lock released for key "${key}":`, releaseResult);
36 }
37}
38
39demo().catch(console.error);
- यह कोड एक डेमो है जो कई ब्राउज़र टैब्स में डेटा और स्टेट को साझा और समकालिक करने के लिए
Shared Worker
का उपयोग करता है। मैसेज-आधारित संचार का उपयोग करके, आप लूज़ कपलिंग के साथ असिंक्रोनस संदेशों का सुरक्षित आदान-प्रदान कर सकते हैं, जिससे विभिन्न संदर्भों के बीच संचार का प्रबंधन आसान हो जाता है। इसके अलावा, RPC का उपयोग करके यह वर्कर के साथ संचार को एक सहज, मेथड-कॉल जैसी शैली में अमूर्त करता है, जिससे रखरखाव और पठनीयता में सुधार होता है।
HTML में परीक्षण
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8" />
5 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6 <title>Shared Worker Demo</title>
7</head>
8<body>
9 <h1>SharedWorker Demo</h1>
10 <p>Open the browser console to see the output.</p>
11
12 <script type="module">
13 import './demo.js';
14 </script>
15</body>
16</html>
डिज़ाइन और संचालन संबंधी विचार
डिज़ाइन और संचालन करते समय, निम्नलिखित बिंदुओं को ध्यान में रखना एक अधिक मजबूत और विस्तार योग्य सिस्टम बनाने में सहायता करेगा।
- आप एक विभेदित यूनियन (टैग्ड यूनियन) अपना सकते हैं, जो
kind
याtype
पर ब्रांचिंग करने की अनुमति देता है। - अनुरोधों को प्रतिक्रियाओं से सही मिलान करने के लिए कोरिलेशन ID का उपयोग करें।
- हार्टबीट और स्वचालित सफ़ाई परित्यक्त लॉक्स को रोक सकते हैं।
- भविष्य के प्रोटोकॉल परिवर्तनों को लचीले रूप से समायोजित करने के लिए वर्ज़निंग लागू करें।
- स्पष्ट त्रुटि कोड परिभाषित करना UI पक्ष पर हैंडलिंग और डीबगिंग को आसान बनाता है।
सारांश
Shared Worker
कई ब्राउज़र टैब्स में डेटा और स्टेट साझा करने के लिए एक मूलभूत तंत्र है।
यहाँ प्रस्तुत संरचना टाइप-सेफ RPC संचार, हार्टबीट के माध्यम से लाइवनेस मॉनिटरिंग, और एक लॉकिंग तंत्र प्रदान करती है, जिससे यह एक ऐसा मज़बूत डिज़ाइन बनता है जिसे प्रोडक्शन में ज्यों का त्यों उपयोग किया जा सकता है।
इस तंत्र के ऊपर, आप निम्नलिखित अनुप्रयोग भी लागू कर सकते हैं।
- IndexedDB एक्सेस का सीरियलाइज़ेशन
- WebSocket कनेक्शनों का एकीकरण और साझाकरण
- कई टैब्स में फैली जॉब कतार बनाना
- थ्रॉटलिंग और प्रगति अधिसूचना वितरण
जैसा कि आप देख सकते हैं, Shared Worker
का उपयोग करके कई टैब्स में डेटा और प्रोसेसिंग को सुरक्षित और कुशलतापूर्वक साझा करना संभव हो जाता है।
आप हमारे YouTube चैनल पर Visual Studio Code का उपयोग करके ऊपर दिए गए लेख के साथ आगे बढ़ सकते हैं। कृपया YouTube चैनल को भी देखें।