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(...) का इस्तेमाल import(someUrl) जैसे मनमुताबिक एक्सप्रेशन के साथ किया जा सकता है. हालांकि, बंडलर, स्टैटिक यूआरएल import('./some-static-url.js') वाले पैटर्न को खास तरीके से इस्तेमाल करते हैं. ऐसा, कंपाइल के समय पता चलने वाली डिपेंडेंसी को पहले से प्रोसेस करने के लिए किया जाता है. इसके बावजूद, इसे अपने चंक में बांट दिया जाता है, जो डाइनैमिक तौर पर लोड होता है.

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

अस्पष्ट रिलेटिव यूआरएल

शायद आप सोच रहे हों कि बंडलर, दूसरे सामान्य पैटर्न का पता क्यों नहीं लगा पाते. उदाहरण के लिए, 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 glue को इंपोर्ट किया जाता है. नीचे दिए गए टूलचेन, आपके लिए new URL(...) पैटर्न को जनरेट कर सकते हैं.

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

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

$ 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 के ज़रिए Rust

WebAssembly के लिए Rust का मुख्य टूलचैन, wasm-pack में भी कई आउटपुट मोड हैं.

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

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

$ wasm-pack build --target web

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

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

इसका मतलब है कि आपके पास अपनी पसंद के थ्रेड एपीआई इस्तेमाल करने का विकल्प नहीं है. हालांकि, Rayon का इस्तेमाल करने पर, इसे wasm-bindgen-rayon अडैप्टर के साथ जोड़ा जा सकता है, ताकि यह वेब पर वर्कर्स को स्पैन कर सके. wasm-bindgen-rayon में इस्तेमाल किए गए JavaScript glue में, 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 टूलचेन में पहले से ही काम कर रहा है.