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

Wasm-pack—WebAssembly का मुख्य Rust टूलचेन है. इसमें कई आउटपुट मोड भी हैं.

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

इसके बजाय, --target web के ज़रिए, Wasm-pack को ब्राउज़र के साथ काम करने वाले 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 टूलचेन में पहले से ही काम कर रहा है.