JavaScript के अलावा, दूसरे संसाधनों को इकट्ठा करना

JavaScript से कई तरह की एसेट को इंपोर्ट और बंडल करने का तरीका जानें.

इंग्वार स्टेपन्यान
इन्ग्वार स्टेपन्यान

मान लीजिए कि किसी वेब ऐप्लिकेशन पर काम किया जा रहा है. ऐसे में, आपको न सिर्फ़ JavaScript मॉड्यूल का, बल्कि अन्य सभी तरह के अन्य संसाधनों का भी इस्तेमाल करना पड़ सकता है—वेब वर्कर (जो JavaScript भी होते हैं, लेकिन सामान्य मॉड्यूल ग्राफ़ का हिस्सा नहीं होते), इमेज, स्टाइलशीट, फ़ॉन्ट, WebAssembly मॉड्यूल वगैरह.

उनमें से कुछ संसाधनों के रेफ़रंस सीधे एचटीएमएल में शामिल किए जा सकते हैं, लेकिन अक्सर वे लॉजिकल तौर पर फिर से इस्तेमाल किए जा सकने वाले कॉम्पोनेंट के साथ जुड़े होते हैं. उदाहरण के लिए, कस्टम ड्रॉपडाउन के JavaScript वाले हिस्से से जुड़ी स्टाइलशीट, टूलबार कॉम्पोनेंट से जुड़ी आइकॉन की इमेज या JavaScript ग्लू से जुड़ा WebAssembly मॉड्यूल. ऐसे मामलों में, सीधे JavaScript मॉड्यूल से संसाधनों का रेफ़रंस देना और उनसे जुड़ा कॉम्पोनेंट लोड होने पर (या अगर) लोड होता है, तो उन्हें डाइनैमिक तौर पर लोड करना ज़्यादा आसान होता है.

JS में इंपोर्ट किए गए कई तरह के एसेट को विज़ुअलाइज़ करने वाला ग्राफ़.

हालांकि, ज़्यादातर बड़े प्रोजेक्ट में ऐसे सिस्टम होते हैं जो कॉन्टेंट को फिर से ऑप्टिमाइज़ और ऑप्टिमाइज़ करते हैं. जैसे, बंडल करना और छोटा करना. न तो वे कोड को एक्ज़ीक्यूट कर सकते हैं और न ही यह अनुमान लगा सकते हैं कि एक्ज़ीक्यूशन से क्या नतीजा मिलेगा. साथ ही, वे JavaScript में लिटरल वैल्यू वाली हर संभावित स्ट्रिंग को पार नहीं कर सकते और यह अनुमान भी नहीं लगा सकते कि यह रिसॉर्स यूआरएल है या नहीं. ऐसे में, JavaScript कॉम्पोनेंट से लोड की गई डाइनैमिक ऐसेट को "देखने" के लिए कैसे सेट किया जा सकता है और उन्हें बिल्ड में शामिल कैसे किया जा सकता है?

बंडलर में कस्टम इंपोर्ट करना

एक सामान्य तरीका यह है कि स्टैटिक इंपोर्ट सिंटैक्स का फिर से इस्तेमाल किया जाए. कुछ बंडलर में, यह फ़ाइल एक्सटेंशन की मदद से फ़ॉर्मैट का अपने-आप पता लगा सकता है, जबकि अन्य प्लगिन को कस्टम यूआरएल स्कीम इस्तेमाल करने की अनुमति देते हैं. इसका उदाहरण नीचे दिया गया है:

// regular JavaScript import
import { loadImg } from './utils.js';

// special "URL imports" for assets
import imageUrl from 'asset-url:./image.png';
import wasmUrl from 'asset-url:./module.wasm';
import workerUrl from 'js-url:./worker.js';

loadImg(imageUrl);
WebAssembly.instantiateStreaming(fetch(wasmUrl));
new Worker(workerUrl);

जब किसी बंडलर प्लगर को पहचान करने वाले किसी एक्सटेंशन या किसी ऐसी खास कस्टम स्कीम (ऊपर दिए गए उदाहरण में asset-url: और js-url:) के साथ कोई इंपोर्ट मिलता है, तो वह रेफ़र की गई एसेट को बिल्ड ग्राफ़ में जोड़ देता है. साथ ही, उसे फ़ाइनल डेस्टिनेशन पर कॉपी करता है, एसेट के टाइप के लिए लागू ऑप्टिमाइज़ेशन करता है, और रनटाइम के दौरान इस्तेमाल किया जाने वाला फ़ाइनल यूआरएल देता है.

इस तरीके के फ़ायदे: JavaScript इंपोर्ट सिंटैक्स का फिर से इस्तेमाल करने से यह गारंटी मिलती है कि सभी यूआरएल, स्टैटिक और मौजूदा फ़ाइल से मिलते-जुलते हैं. इससे बिल्ड सिस्टम के लिए ऐसी डिपेंडेंसी का पता लगाना आसान हो जाता है.

हालांकि, इसमें एक अहम कमी है: ऐसा कोड सीधे ब्राउज़र पर काम नहीं कर सकता, क्योंकि ब्राउज़र उन कस्टम इंपोर्ट स्कीम या एक्सटेंशन को मैनेज करने का तरीका नहीं जानता. अगर आप सभी कोड को कंट्रोल करते हैं और इसके लिए डेवलपमेंट के लिए किसी बंडलर पर भरोसा करते हैं, तो यह ठीक हो सकता है. हालांकि, रुकावटों को कम करने के लिए, कम से कम डेवलपमेंट के दौरान सीधे ब्राउज़र में JavaScript मॉड्यूल इस्तेमाल करना आम बात है. किसी छोटे डेमो पर काम कर रहे किसी व्यक्ति को शायद प्रोडक्शन के दौरान भी बंडलर की ज़रूरत न पड़े.

ब्राउज़र और बंडलर के लिए यूनिवर्सल पैटर्न

अगर फिर से इस्तेमाल किए जा सकने वाले कॉम्पोनेंट पर काम किया जा रहा है, तो आपको इसे किसी भी माहौल में काम करने देना चाहिए, फिर चाहे वह सीधे ब्राउज़र में इस्तेमाल किया जाता हो या किसी बड़े ऐप्लिकेशन के हिस्से के तौर पर पहले से बनाया गया हो. ज़्यादातर आधुनिक बंडलर, JavaScript मॉड्यूल में नीचे दिए गए पैटर्न को स्वीकार करके ऐसा करने की अनुमति देते हैं:

new URL('./relative-path', import.meta.url)

टूल की मदद से इस पैटर्न का पता लगाया जा सकता है. यह किसी खास सिंटैक्स की तरह होता है, लेकिन यह एक मान्य JavaScript एक्सप्रेशन है जो सीधे ब्राउज़र पर भी काम करता है.

इस पैटर्न का इस्तेमाल करते समय, ऊपर दिया गया उदाहरण इस तरह से फिर से लिखा जा सकता है:

// regular JavaScript import
import { loadImg } from './utils.js';

loadImg(new URL('./image.png', import.meta.url));
WebAssembly.instantiateStreaming(
  fetch(new URL('./module.wasm', import.meta.url)),
  { /* … */ }
);
new Worker(new URL('./worker.js', import.meta.url));

यह कैसे काम करती है? चलिए, इसे बांटते हैं. new URL(...) कंस्ट्रक्टर, पहले आर्ग्युमेंट के तौर पर रिलेटिव यूआरएल को लेता है और उसे दूसरे आर्ग्युमेंट के तौर पर दिए गए ऐब्सलूट यूआरएल से रिज़ॉल्व करता है. हमारे मामले में, दूसरा आर्ग्युमेंट import.meta.url है, जो मौजूदा JavaScript मॉड्यूल का यूआरएल देता है. इसलिए, पहला आर्ग्युमेंट इससे मिलता-जुलता कोई भी पाथ हो सकता है.

यह डाइनैमिक इंपोर्ट की तरह ही है. import(someUrl) जैसे आर्बिट्रेरी एक्सप्रेशन के साथ import(...) का इस्तेमाल किया जा सकता है. वहीं, बंडलर, स्टैटिक यूआरएल import('./some-static-url.js') वाले पैटर्न को खास तौर पर लागू करता है. इससे कंपाइलर के समय पर डिपेंडेंसी पहले से प्रोसेस हो पाती है. साथ ही, यह डाइनैमिक तौर पर लोड होने वाले अलग-अलग हिस्सों में बंट जाती है.

इसी तरह, new URL(relativeUrl, customAbsoluteBase) जैसे आर्बिट्रेरी एक्सप्रेशन के साथ new URL(...) का इस्तेमाल किया जा सकता है. हालांकि, बंडलर को पहले से प्रोसेस करने और मुख्य JavaScript के साथ डिपेंडेंसी शामिल करने के लिए, new URL('...', import.meta.url) पैटर्न एक साफ़ सिग्नल देता है.

अस्पष्ट मिलते-जुलते यूआरएल

आप सोच रहे होंगे कि बंडलर दूसरे सामान्य पैटर्न का पता क्यों नहीं लगा सकते—उदाहरण के लिए, new URL रैपर के बिना fetch('./module.wasm')?

इसकी वजह यह है कि इंपोर्ट स्टेटमेंट के उलट, किसी भी डाइनैमिक अनुरोध का समाधान, मौजूदा JavaScript फ़ाइल के बजाय, दस्तावेज़ के हिसाब से ही होता है. मान लें कि आपके पास नीचे दिया गया स्ट्रक्चर है:

  • index.html:
    html <script src="src/main.js" type="module"></script>
  • src/
    • main.js
    • module.wasm

अगर आपको main.js से module.wasm को लोड करना है, तो हो सकता है कि आप fetch('./module.wasm') जैसे मिलते-जुलते पाथ का इस्तेमाल करें.

fetch को उस JavaScript फ़ाइल का यूआरएल नहीं पता जिसमें उसे एक्ज़ीक्यूट किया गया है. इसके बजाय, यह दस्तावेज़ से मिलते-जुलते यूआरएल का समाधान करता है. इस वजह से, fetch('./module.wasm') तय किए गए http://example.com/src/module.wasm के बजाय, http://example.com/module.wasm को लोड करने की कोशिश कर सकता है. हालांकि, यह अनुमति नहीं मिलने पर, वह गलत तरीके से कोई दूसरा रिसॉर्स लोड कर सकता है.

मिलते-जुलते यूआरएल को new URL('...', import.meta.url) में रैप करके, इस समस्या से बचा जा सकता है. साथ ही, यह गारंटी दी जा सकती है कि किसी भी दिए गए यूआरएल को किसी लोडर को भेजे जाने से पहले, मौजूदा JavaScript मॉड्यूल (import.meta.url) के यूआरएल के हिसाब से ठीक कर दिया जाएगा.

fetch('./module.wasm') को fetch(new URL('./module.wasm', import.meta.url)) से बदलें. इससे, यह उम्मीद के मुताबिक WebAssembly मॉड्यूल को लोड कर देगा. साथ ही, इससे बंडलर को एक तरीका भी मिलेगा, ताकि वे बिल्ड के दौरान उन मिलते-जुलते पाथ को ढूंढ सकें.

टूलिंग सहायता

बंडलर

ये बंडलर पहले से ही new URL स्कीम के साथ काम करते हैं:

WebAssembly

WebAssembly के साथ काम करते समय, आम तौर पर Wasm मॉड्यूल को हाथ से लोड नहीं किया जाता. इसकी जगह, टूलचेन से निकलने वाले JavaScript ग्लू को इंपोर्ट किया जाता है. नीचे दिए गए टूलचेन, आपके डिवाइस के हुड के नीचे बताए गए new URL(...) पैटर्न का उत्सर्जन कर सकते हैं.

Emscripten के ज़रिए C/C++

Emscripten का इस्तेमाल करते समय, इसे सामान्य स्क्रिप्ट के बजाय ES6 मॉड्यूल के तौर पर JavaScript ग्लू छोड़ने के लिए कहा जा सकता है. ऐसा, नीचे दिए गए विकल्पों में से किसी एक का इस्तेमाल करके किया जा सकता है:

$ emcc input.cpp -o output.mjs
## or, if you don't want to use .mjs extension
$ emcc input.cpp -o output.js -s EXPORT_ES6

इस विकल्प का इस्तेमाल करने पर आउटपुट, हुड के नीचे new URL(..., import.meta.url) पैटर्न का इस्तेमाल करेगा, ताकि बंडलर अपने-आप इससे जुड़ी Wasm फ़ाइल ढूंढ सकें.

-pthread फ़्लैग जोड़कर, इस विकल्प का इस्तेमाल WebAssembly थ्रेड के साथ भी किया जा सकता है:

$ emcc input.cpp -o output.mjs -pthread
## or, if you don't want to use .mjs extension
$ emcc input.cpp -o output.js -s EXPORT_ES6 -pthread

इस स्थिति में, जनरेट किए गए वेब वर्कर को उसी तरीके से शामिल किया जाएगा और वह बंडलर और ब्राउज़र दोनों पर समान रूप से खोजा जा सकेगा.

Wasm-pack / Wasm-bindgen से रस्ट करें

vasm-pack—WebAssembly के लिए मौजूद मुख्य Rust टूलचेन में भी कई आउटपुट मोड हैं.

डिफ़ॉल्ट रूप से, इससे ऐसा JavaScript मॉड्यूल निकलेगा जो WebAssembly ESM इंटिग्रेशन के प्रस्ताव पर निर्भर करता है. लिखने के समय, यह प्रस्ताव अब भी एक्सपेरिमेंट के तौर पर उपलब्ध है. साथ ही, आउटपुट सिर्फ़ Webpack के साथ बंडल किए जाने पर काम करेगा.

इसके बजाय, Wasm-pack से --target web के ज़रिए, ब्राउज़र के साथ काम करने वाले ES6 मॉड्यूल का उत्सर्जन करने के लिए कहा जा सकता है:

$ wasm-pack build --target web

आउटपुट में बताए गए new URL(..., import.meta.url) पैटर्न का इस्तेमाल किया जाएगा और Wasm फ़ाइल भी बंडलर के ज़रिए अपने-आप खोजी जाएगी.

अगर आपको Rust के साथ WebAssembly थ्रेड का इस्तेमाल करना है, तो कहानी थोड़ी मुश्किल है. ज़्यादा जानने के लिए, गाइड का संबंधित सेक्शन देखें.

शॉर्ट वर्शन में, आर्बिट्ररी थ्रेड एपीआई का इस्तेमाल नहीं किया जा सकता. हालांकि, अगर Rayon का इस्तेमाल किया जा रहा है, तो इसे vasm-bindgen-rayon अडैप्टर के साथ जोड़ा जा सकता है, ताकि यह वेब पर वर्कर की संख्या बढ़ा सके. Wasm-bindgen-rayon के JavaScript ग्लू का इस्तेमाल किया जाता है, जिसमें हुड के तहत new URL(...) पैटर्न भी शामिल होता है. इससे वर्कर को भी आसानी से खोजा जा सकेगा और बंडलर इसमें शामिल कर सकेंगे.

आने वाले समय में मिलने वाली सुविधाएं

import.meta.resolve

खास तौर पर, import.meta.resolve(...) कॉल करने पर, आने वाले समय में आपको बेहतर सुविधाएं मिलेंगी. इसकी मदद से, खास पैरामीटर को मौजूदा मॉड्यूल के मुकाबले ज़्यादा आसान तरीके से, अतिरिक्त पैरामीटर के बिना समाधान किया जा सकेगा:

new URL('...', import.meta.url)
await import.meta.resolve('...')

यह इंपोर्ट मैप और कस्टम रिज़ॉल्वर के साथ बेहतर तरीके से इंटिग्रेट हो जाएगा, क्योंकि यह import के मॉड्यूल रिज़ॉल्यूशन सिस्टम से ही गुज़रता है. यह बंडलर के लिए भी ज़्यादा कारगर साबित होगा, क्योंकि यह एक स्टैटिक सिंटैक्स है, जो URL जैसे रनटाइम एपीआई पर निर्भर नहीं होता.

import.meta.resolve को पहले ही Node.js में एक प्रयोग के तौर पर लागू किया जा चुका है, लेकिन अब भी ऐसे कुछ सवाल हैं जिनका हल नहीं हुआ है. साथ ही, यह भी बताया गया है कि इसे वेब पर कैसे काम करना चाहिए.

दावे इंपोर्ट करें

इंपोर्ट के दावे एक नई सुविधा है. इसकी मदद से, ECMAScript मॉड्यूल के अलावा, किसी दूसरे टाइप के ऑडियो इंपोर्ट किए जा सकते हैं. फ़िलहाल, सिर्फ़ JSON फ़ॉर्मैट में:

foo.json:

{ "answer": 42 }

main.mjs:

import json from './foo.json' assert { type: 'json' };
console.log(json.answer); // 42

हालांकि, बंडलर इन्हें इस्तेमाल कर सकते हैं और new URL पैटर्न के तहत आने वाले इस्तेमाल के उदाहरणों की जगह इस्तेमाल कर सकते हैं. हालांकि, इंपोर्ट के दावे के टाइप, हर केस के हिसाब से जोड़े जाते हैं. फ़िलहाल, वे सिर्फ़ JSON फ़ॉर्मैट में उपलब्ध हैं और सीएसएस मॉड्यूल जल्द ही उपलब्ध होंगे. हालांकि, दूसरी तरह की एसेट के लिए अब भी सामान्य तरीके की ज़रूरत होगी.

इस सुविधा के बारे में ज़्यादा जानने के लिए, v8.dev की सुविधा के बारे में जानकारी देखें.

नतीजा

जैसा कि आपको दिख रहा है कि वेब पर नॉन-JavaScript संसाधनों को शामिल करने के कई तरीके हैं, लेकिन उनकी कई कमियां हैं और ये अलग-अलग टूलचेन में काम नहीं करते. आने वाले समय के प्रस्तावों से हमें ऐसी एसेट को खास सिंटैक्स के साथ इंपोर्ट करने में मदद मिल सकती है. हालांकि, फ़िलहाल ऐसा नहीं किया जा सकता.

तब तक, new URL(..., import.meta.url) पैटर्न सबसे सटीक समाधान है, जो मौजूदा समय में ब्राउज़र, अलग-अलग बंडलर, और WebAssembly टूलचेन में पहले से ही काम करता है.