JavaScript बLOAT के तीन स्तंभ
(43081j.com)- npm ecosystem में dependency tree का बLOAT एक बड़ी समस्या के रूप में देखा जाता है, और इसके पीछे पुराने runtime support, atomic package structure, और पुराने ponyfill का उपयोग मुख्य कारण हैं
- पुराने engine compatibility और cross-realm safety के कारण बनाए रखे गए छोटे utility packages आधुनिक environment में भी अनावश्यक रूप से बचे हुए हैं
- Atomic architecture का लक्ष्य reusability बढ़ाना था, लेकिन व्यवहार में यह duplication, security, और maintenance cost बढ़ाने वाली अक्षम संरचना बन गई है
- जिन features को सभी engines पहले से support करते हैं, उनके लिए बने पुराने ponyfill packages हटाए नहीं गए, जिससे अनावश्यक downloads और management burden पैदा होता है
- Community, e18e, knip, और module-replacements जैसे tools के जरिए अनावश्यक dependencies हटाने और native features पर शिफ्ट करने की दिशा में काम कर रही है
JavaScript dependency बLOAT के तीन स्तंभ
- e18e community के बढ़ने के साथ performance-केंद्रित contributions बढ़ रहे हैं, और अनावश्यक या unmaintained packages को हटाने की cleanup activity चल रही है
- npm ecosystem में dependency tree का बLOAT (dependency bloat) एक मुख्य समस्या है, और पुराने runtime support, atomic package structure, तथा पुराने ponyfill का उपयोग इसके प्रमुख कारण माने जाते हैं
1. पुराना runtime support (safety और realm सहित)
- npm tree में
is-string,hasownजैसे कई छोटे utility packages मौजूद हैं, और ये मुख्यतः नीचे दिए गए तीन कारणों से बने हुए हैं- बहुत पुराने engines (जैसे ES3, IE6/7, शुरुआती Node.js) का support
-
global namespace modification से सुरक्षा
- cross-realm values को handle करना
-
पुराने engines का support
- ES3 environment में
Array.prototype.forEach,Object.keys,Object.definePropertyजैसी ES5 features मौजूद नहीं थीं - ऐसे environment में direct implementation या polyfill का उपयोग करना पड़ता है
- सबसे अच्छा समाधान upgrade है, लेकिन कुछ users अब भी पुराने versions पर टिके हुए हैं
- ES3 environment में
-
global namespace modification से सुरक्षा
- Node अंदरूनी रूप से primordials की अवधारणा का उपयोग करता है, जिसमें global objects को शुरुआत में wrap करके modification से सुरक्षित रखा जाता है
- उदाहरण के लिए, अगर
Mapको redefine कर दिया जाए तो Node खुद टूट सकता है, इसलिए Node original reference को बनाए रखता है - कुछ package maintainers यही तरीका सामान्य packages में भी अपनाते हैं और
math-intrinsicsजैसे safety-केंद्रित packages का उपयोग करते हैं
-
Cross-realm values
- iframe के बीच object pass करने पर
instanceofcheck fail हो सकता है - उदाहरण:
window.RegExp !== iframeWindow.RegExp chaiजैसे test frameworks realm के बीच type check के लिएObject.prototype.toString.call(val)तरीका इस्तेमाल करते हैंis-stringजैसे packages ऐसी cross-realm compatibility के लिए मौजूद हैं
- iframe के बीच object pass करने पर
-
समस्या
- ज़्यादातर developers modern Node या evergreen browsers का उपयोग करते हैं, इसलिए ऐसी compatibility की ज़रूरत नहीं होती
- लेकिन ये packages सामान्य dependency tree के “hot path” में शामिल हो जाते हैं, इसलिए सबको इसकी cost चुकानी पड़ती है
2. Atomic architecture
- कुछ developers का मानना है कि packages को जितना संभव हो छोटी units में बाँटना चाहिए ताकि reusable building blocks बनाए जा सकें
- नतीजतन
shebang-regex,arrify,slash,path-key,onetime,is-wslजैसे बेहद सूक्ष्म रूप से विभाजित packages बड़ी संख्या में मौजूद हैं - उदाहरण:
shebang-regexमें सिर्फ एक line की regex है (/^#!(.*)/) -
समस्या
- ज़्यादातर atomic packages reuse नहीं होते या उनका सिर्फ एक consumer होता है
- उदाहरण:
shebang-regex→ सिर्फshebang-commandउपयोग करता हैcli-boxes→ सिर्फboxen,inkउपयोग करते हैंonetime→ सिर्फrestore-cursorउपयोग करता है
- ऐसे मामलों में यह inline code जैसा ही है, लेकिन npm requests, extraction, bandwidth जैसी अतिरिक्त cost पैदा होती है
-
duplication की समस्या
- उदाहरण:
nuxt@4.4.2dependency tree मेंis-docker,is-stream,is-wsl,path-keyआदि 2-2 versions में duplicate मौजूद हैं - अगर इन्हें inline code से बदला जाए तो version conflicts या resolution cost खत्म हो जाती है, इसलिए duplication cost लगभग शून्य हो सकती है
- उदाहरण:
-
supply chain risk का विस्तार
- packages की संख्या जितनी अधिक होगी, security और maintenance risk उतना बढ़ेगा
- वास्तव में, एक maintainer के कई छोटे packages manage करने के दौरान उसका account hack हो गया और सैकड़ों packages एक साथ compromise हो गए थे
- साधारण code (
Array.isArray(val) ? val : [val]) को अलग package बनाने की बजाय inline handle किया जा सकता है
-
निष्कर्ष
- Atomic architecture, अपने इरादे के विपरीत, अक्षम और जोखिमपूर्ण संरचना में बदल गई है
- अधिकांश users के लिए किसी वास्तविक लाभ के बिना पूरा ecosystem इसकी cost उठाता है
3. पुराने Ponyfill
- Polyfill वह code है जो engine द्वारा unsupported feature को environment में जोड़ता है, जबकि Ponyfill environment को बदले बिना सीधे import करके इस्तेमाल की जाने वाली alternative implementation है
- उदाहरण:
@fastly/performance-observer-polyfillpolyfill और ponyfill दोनों उपलब्ध कराता है -
समस्या
- Ponyfill पहले उपयोगी थे, लेकिन target feature सभी engines में support होने के बाद भी हटाए नहीं गए
- उदाहरण:
globalthis(2019 से support, साप्ताहिक 4.9 करोड़ downloads)indexof(2010 से support, साप्ताहिक 23 लाख downloads)object.entries(2017 से support, साप्ताहिक 3.5 करोड़ downloads)
- ऐसे packages ज़्यादातर सिर्फ इसलिए बचे हैं क्योंकि उन्हें हटाया नहीं गया
- जब सभी LTS engines किसी feature को support करने लगें, तो ponyfill हटा दिए जाने चाहिए
बLOAT कम करने के उपाय
- dependency tree की गहरी nesting के कारण cleanup मुश्किल है, लेकिन community collaboration से सुधार संभव है
- हर developer को खुद से पूछना चाहिए, “क्या यह package सच में ज़रूरी है?” और अगर नहीं, तो issue उठाना या alternative package ढूँढना चाहिए
- module-replacements project ऐसे packages की सूची देता है जिन्हें native features से बदला जा सकता है
-
knip का उपयोग
- knip unused dependencies और dead code detect करने का tool है
- यह सीधा समाधान नहीं है, लेकिन cleanup शुरू करने के लिए उपयोगी है
-
e18e CLI का उपयोग
@e18e/cli analyzecommand से replace की जा सकने वाली dependencies detect की जा सकती हैं- उदाहरण:
chalk→picocolorsमें automatic migration - आगे चलकर environment के आधार पर Node के
styleTextजैसे native features recommend करने की योजना है
-
npmgraph का उपयोग
- npmgraph.js.org dependency tree visualization tool है
- उदाहरण:
eslint@10.1.0tree मेंfind-upbranch अलग-थलग पड़ी है - साधारण file lookup के लिए 6 packages की ज़रूरत नहीं होनी चाहिए, इसलिए
empathicजैसे छोटे alternatives इस्तेमाल किए जा सकते हैं
-
module replacements project
- Community replace किए जा सकने वाले packages और native features की mapping dataset को maintain करती है
- codemods project के जरिए automatic migration भी support किया जाता है
निष्कर्ष
- मौजूदा बLOAT ऐसी संरचना है जिसमें पुरानी compatibility और अजीब architecture बनाए रखने वाले कुछ users के कारण पूरी community cost चुकाती है
- पहले यह अपरिहार्य था, लेकिन आज जब modern engines और APIs काफ़ी विकसित हो चुके हैं, यह अनावश्यक बोझ बन चुका है
- आगे चलकर इन कुछ users को अलग stack maintain करना चाहिए, और बाकी ecosystem को हल्के और modern codebase की ओर बढ़ना चाहिए
- e18e और npmx जैसे projects documentation और tooling के माध्यम से इसे support कर रहे हैं, और हर developer को अपनी dependencies की समीक्षा करके पूछना चाहिए: “यह क्यों ज़रूरी है?”
- सफाई सब मिलकर कर सकते हैं
2 टिप्पणियां
जब मैं भी लाइब्रेरी बनाता हूँ, तो अभी CJS build देता हूँ
लेकिन 2026 में भी जिन लाइब्रेरीज़ में ESM examples तक नहीं हैं और सब कुछ
require-based है, उनका थोड़ा update हो जाना अच्छा होगा।Hacker News की राय
मुझे लगता है कि आजकल dependency-free JavaScript के साथ development करना सबसे अच्छा रास्ता है
JS/CSS standard libraries भी शानदार हैं, और static analysis (TypeScript का JSDoc check), ES modules, web components वगैरह भी काफ़ी शक्तिशाली हैं
लोग कहते हैं कि यह तरीका scalability या maintenance के लिए नुकसानदेह है, लेकिन मेरे अनुभव में इससे उल्टा ज़्यादा सरल और आसानी से बदली जा सकने वाली structure बनाए रखना संभव हुआ
framework या build tools जो ज़्यादातर काम करते हैं, उन्हें browser की built-in features और vanilla patterns से बदला जा सकता है
लेकिन यह तरीका अभी भी काफ़ी अनजान इलाक़ा है, इसलिए समस्या यह है कि ज़्यादातर tutorial ecosystem बड़े frameworks के इर्द-गिर्द घूमता है
वास्तव में React code को पूरी तरह vanilla में ले जाने पर भी modularity बनी रहती है और code की लंबाई लगभग 1.5 गुना ही बढ़ती है, लेकिन dependencies न होने की वजह से performance और बेहतर हो जाती है
बेशक dependencies बुरी चीज़ नहीं हैं। बस बहुत से developers इस fixed mindset में फँसे हैं कि “इन्हें ज़रूर इस्तेमाल करना ही होगा”
उदाहरण के लिए, मैं ऐसे site बनाता हूँ जिनमें map features बहुत होते हैं, इसलिए mapbox/maplibre/openlayers जैसी ऐसी libraries इस्तेमाल करनी पड़ती हैं जिनका कोई असली विकल्प नहीं है
client को भी migration cost पर एक पैसा खर्च नहीं करना पड़ा
इस लेख की तरह, यह जानने की जिज्ञासा है कि model updates को कैसे handle किया जाता है
उल्टा large codebase को कम लोगों के साथ maintain करना और आसान हो गया है
आजकल के tools की वजह से पहले की तुलना में ख़ुद implementation करना बहुत आसान हो गया है, और यह agentic engineering के साथ भी अच्छी तरह फिट बैठता है
लेख अच्छी तरह लिखा गया है, भावनात्मक हुए बिना समस्या को साफ़-साफ़ समझाता है
मुझे लगता है कि JS के पास ठीक-ठाक standard library न होना इस स्थिति की एक वजह है
लेख अच्छा है, लेकिन मुझे लगता है कि समस्या की जड़ अनावश्यक जोड़ (=bloat) ही है
मैं Saint-Exupéry की इस बात को उद्धृत करना चाहूँगा: “पूर्णता तब नहीं आती जब जोड़ने के लिए कुछ न बचे, बल्कि तब आती है जब हटाने के लिए कुछ न बचे”
ज़्यादातर software इस सवाल से नहीं लिखे जाते कि “इसे और elegant कैसे बनाएँ?”, बल्कि इस तरह लिखे जाते हैं कि “इसमें और आसानी से क्या जोड़ा जा सकता है?”
जवाब हमेशा
npm i more-stuffहोता हैDemosthenes और Cicero के contrast की तरह, वही code अच्छा है जिसमें कुछ भी और हटाया न जा सके
JS को अतीत और भविष्य दोनों तरह के browser compatibility का ध्यान रखना पड़ता है, और UI-centric language होने की वजह से accessibility, internationalization, mobile support वगैरह से आकार बढ़ता जाता है
कई मामलों में यह छिपे हुए technical debt की समस्या लगती है
compile target को ESx तक न बढ़ाना, और packages या implementation को update न करना इसका कारण है
ES5 को सभी browsers में supported हुए अब 13 साल हो चुके हैं (caniuse.com/es5)
दोनों ही अपनी हरकतों को एक feature मानते हैं, और कई popular packages maintain करते हैं
इसलिए बदलाव मुश्किल है। कभी-कभी community आलोचना करती है, लेकिन उनके पास भी अपनी-अपनी दलीलें होती हैं
Babel से पुराने version में transpile करने पर code फूल जाता है और धीमा हो जाता है, और फिर भी पुराने browsers में CSS या JS feature limitations की वजह से वह ठीक से चलता नहीं
यहाँ तक कि polyfill ने भी कभी-कभी समस्याएँ पैदा की हैं (ऐसा exponent operator polyfill जो BigInt को handle नहीं कर पाया)
consoles, TVs, पुराने Android, iPod touch, Facebook in-app browser जैसी कई environments मौजूद हैं
इसलिए मैं सिर्फ़ एक external module रखता हूँ, और बाकी सब transpiler configuration से संभालता हूँ
पहले async tracking के लिए setTimeout वगैरह को override किया जाता था, लेकिन अब signals से इसे काफ़ी सरल तरीके से handle किया जा सकता है
मुझे लगता है कुछ package authors download count बढ़ाने के लिए dependency tree को जानबूझकर टुकड़ों में बाँटते हैं
7 lines वाले package का होना ही बेतुका है। lockfile metadata code से बड़ा होता है
पहले create-react-app की dependencies में 5% एक ही author के mini packages थे
has-symbols, is-string, ljharb जैसे उदाहरण हैं
जैसे Anthropic, npm download count ज़्यादा रखने वाले open source maintainers को free Claude देता है
download count की दौड़ उल्टा जोखिम बढ़ाती है
लेकिन दूसरी cultures में इसे उल्टा अच्छी बात माना जाता है
JS ecosystem की आलोचना करने से पहले 30 years of br tags पढ़ना अच्छा रहेगा
इससे JS और tools की evolution process समझने में मदद मिलती है
सिर्फ़ यह कहना कि “JS developers ही समस्या हैं” engineering mindset की कमी दिखाता है
हमें हमेशा बेहतर theory और practice के बारे में सोचना चाहिए
software की दुनिया बहुत तेज़ी से बदलती है, इसलिए ख़ुद अपनी “fake funeral” करके पुरानी practices छोड़ने की ज़रूरत है
मैं 9 साल पुराने Node.js codebase को maintain कर रहा हूँ, और उसमें सिर्फ़ 8 dependencies हैं, वह भी सभी बिना transitive dependencies के
Node की built-in features को प्राथमिकता देता हूँ, और ज़रूरत के हिस्से ही ख़ुद implement करता हूँ
अब यह पहले से कहीं ज़्यादा stable है और stress भी कम है
Deno की standard library भी शानदार है, इसलिए runtime की default features के साथ कुछ ही packages में काफ़ी app बनाया जा सकता है
JS अगर सावधानी से इस्तेमाल किया जाए तो काफ़ी अच्छी भाषा है
is-stringजैसे package का cross-realm safety वाला दावा समझ में आता है, लेकिन असल में ऐसी situations बहुत कम होती हैंnpm ने publish करना बहुत आसान बना दिया, और “module को तोड़कर publish करो” वाली philosophy ज़रूरत से ज़्यादा फैल गई — यही समस्या है
consumer dependency tree का audit नहीं करते, बस install कर लेते हैं, इसलिए optional cost default cost बन जाती है
ponyfill की समस्या automation से हल की जा सकती है
उदाहरण के लिए, ऐसा Renovate-style bot मददगार हो सकता है जो Node LTS versions में पहले से supported features को auto-detect करके हटा दे
हमारी internal PWA का सिर्फ़ एक ही principle है:
“Chrome के latest version में upgrade करो। फिर भी समस्या हो तो उसके बाद देखेंगे”
Safari कम memory इस्तेमाल करता है, यह समझ में आता है, लेकिन policy के स्तर पर standardize करना ज़्यादा efficient है
“ES3 (IE6/7 स्तर) तक support करना चाहिए” जैसी बात सच में समझना मुश्किल है
security के लिहाज़ से तो banking sites को भी ऐसे पुराने browsers ब्लॉक कर देने चाहिए
Webpack, Babel, polyfill stack को upgrade करना बड़ा काम है, इसलिए वे उसे वैसे ही छोड़ देते हैं
यह “अगर टूटा नहीं है तो ठीक मत करो” वाली culture है