Closure Compiler की ओर से लगाई गई पाबंदियों के बारे में जानकारी

Closure Compiler को JavaScript इनपुट के लिए, कुछ पाबंदियों का पालन करना होता है. कंपाइलर को ऑप्टिमाइज़ेशन का जितना ज़्यादा लेवल करने के लिए कहा जाएगा, कंपाइलर इनपुट JavaScript पर उतनी ही ज़्यादा पाबंदियां लगाएगा.

इस दस्तावेज़ में, ऑप्टिमाइज़ेशन के हर लेवल के लिए मुख्य पाबंदियों के बारे में बताया गया है. कंपाइलर की ओर से की गई अन्य मान्यताओं के बारे में जानने के लिए, यह विकी पेज भी देखें.

ऑप्टिमाइज़ेशन के सभी लेवल के लिए पाबंदियां

कंपाइलर, प्रोसेस की गई सभी JavaScript पर ये दो पाबंदियां लगाता है. ये पाबंदियां, ऑप्टिमाइज़ेशन के सभी लेवल के लिए होती हैं:

  • कंपाइलर सिर्फ़ ECMAScript को पहचानता है.

    ECMAScript 5, JavaScript का ऐसा वर्शन है जो लगभग हर जगह काम करता है. हालांकि, कंपाइलर ECMAScript 6 की कई सुविधाओं के साथ भी काम करता है. कंपाइलर, सिर्फ़ आधिकारिक भाषा की सुविधाओं के साथ काम करता है.

    ब्राउज़र के हिसाब से बनाई गई ऐसी सुविधाएँ जो ECMAScript भाषा के सही स्पेसिफ़िकेशन के मुताबिक हैं, कंपाइलर के साथ ठीक से काम करेंगी. उदाहरण के लिए, ActiveX ऑब्जेक्ट को मान्य JavaScript सिंटैक्स का इस्तेमाल करके बनाया जाता है. इसलिए, ActiveX ऑब्जेक्ट बनाने वाला कोड कंपाइलर के साथ काम करता है.

    कंपाइलर को मैनेज करने वाले लोग, भाषा के नए वर्शन और उनकी सुविधाओं को सपोर्ट करने के लिए लगातार काम करते हैं. प्रोजेक्ट, --language_in फ़्लैग का इस्तेमाल करके यह तय कर सकते हैं कि उन्हें ECMAScript के किस वर्शन का इस्तेमाल करना है.

  • कंपाइलर, टिप्पणियों को सेव नहीं करता है.

    कंपाइलर के ऑप्टिमाइज़ेशन के सभी लेवल पर टिप्पणियां हटा दी जाती हैं. इसलिए, खास तौर पर फ़ॉर्मैट की गई टिप्पणियों पर निर्भर रहने वाला कोड, कंपाइलर के साथ काम नहीं करता.

    उदाहरण के लिए, कंपाइलर टिप्पणियों को सेव नहीं करता है. इसलिए, JScript की "शर्त के साथ की गई टिप्पणियों" का सीधे तौर पर इस्तेमाल नहीं किया जा सकता. हालांकि, इस पाबंदी को हटाने के लिए, शर्त के साथ की गई टिप्पणियों को eval() एक्सप्रेशन में रैप किया जा सकता है. कंपाइलर, बिना किसी गड़बड़ी के इस कोड को प्रोसेस कर सकता है:

     x = eval("/*@cc_on 2+@*/ 0");

    ध्यान दें: @preserve एनोटेशन का इस्तेमाल करके, कंपाइलर के आउटपुट में सबसे ऊपर ओपन सोर्स लाइसेंस और अन्य ज़रूरी टेक्स्ट शामिल किया जा सकता है.

SIMPLE_OPTIMIZATIONS के लिए पाबंदियां

सिंपल ऑप्टिमाइज़ेशन लेवल, कोड का साइज़ कम करने के लिए फ़ंक्शन पैरामीटर, लोकल वैरिएबल, और स्थानीय तौर पर तय किए गए फ़ंक्शन के नाम बदलता है. हालांकि, कुछ JavaScript कंस्ट्रक्ट, नाम बदलने की इस प्रोसेस को रोक सकते हैं.

SIMPLE_OPTIMIZATIONS का इस्तेमाल करते समय, इन चीज़ों और तरीकों से बचें:

  • with:

    with का इस्तेमाल करने पर, कंपाइलर एक ही नाम वाले लोकल वैरिएबल और ऑब्जेक्ट प्रॉपर्टी के बीच अंतर नहीं कर पाता. इसलिए, वह नाम के सभी इंस्टेंस का नाम बदल देता है.

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

  • eval():

    कंपाइलर, eval() के स्ट्रिंग आर्ग्युमेंट को पार्स नहीं करता है. इसलिए, वह इस आर्ग्युमेंट में मौजूद किसी भी सिंबल का नाम नहीं बदलेगा.

  • फ़ंक्शन या पैरामीटर के नामों के स्ट्रिंग प्रज़ेंटेशन:

    कंपाइलर, फ़ंक्शन और फ़ंक्शन पैरामीटर के नाम बदलता है. हालांकि, यह आपके कोड में मौजूद ऐसी किसी भी स्ट्रिंग में बदलाव नहीं करता जो नाम के हिसाब से फ़ंक्शन या पैरामीटर को रेफ़र करती हैं. इसलिए, आपको अपने कोड में फ़ंक्शन या पैरामीटर के नामों को स्ट्रिंग के तौर पर नहीं दिखाना चाहिए. उदाहरण के लिए, Prototype लाइब्रेरी फ़ंक्शन argumentNames(), किसी फ़ंक्शन के पैरामीटर के नाम पाने के लिए Function.toString() का इस्तेमाल करता है. हालांकि, argumentNames() आपको अपने कोड में आर्ग्युमेंट के नाम इस्तेमाल करने के लिए प्रेरित कर सकता है, लेकिन सिंपल मोड में कंपाइल करने पर इस तरह का रेफ़रंस काम नहीं करता.

ADVANCED_OPTIMIZATIONS के लिए पाबंदियां

ADVANCED_OPTIMIZATIONS कंपाइलेशन लेवल, SIMPLE_OPTIMIZATIONS की तरह ही ट्रांसफ़ॉर्मेशन करता है. साथ ही, यह प्रॉपर्टी, वैरिएबल, और फ़ंक्शन के नाम बदलने की सुविधा भी जोड़ता है. इसके अलावा, यह डेड कोड को हटाता है और प्रॉपर्टी को फ़्लैट करता है. इन नए पास से, इनपुट की गई JavaScript पर अतिरिक्त पाबंदियां लगाई जाती हैं. आम तौर पर, JavaScript की डाइनैमिक सुविधाओं का इस्तेमाल करने से, आपके कोड पर सही स्टैटिक विश्लेषण नहीं हो पाएगा.

ग्लोबल वैरिएबल, फ़ंक्शन, और प्रॉपर्टी का नाम बदलने के असर:

ADVANCED_OPTIMIZATIONS का नाम बदलकर, दुनिया भर में एक जैसा कर दिया गया है. इससे ये गतिविधियां खतरनाक हो गई हैं:

  • ऐसे बाहरी रेफ़रंस जिनके बारे में नहीं बताया गया है:

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

  • बाहरी कोड में, एक्सपोर्ट नहीं किए गए इंटरनल नामों का इस्तेमाल करना:

    कंपाइल किए गए कोड को उन सभी सिंबल को एक्सपोर्ट करना होगा जिनका इस्तेमाल, कंपाइल न किए गए कोड में किया गया है. ऐडवांस कंपाइलेशन और एक्सटर्न में, सिंबल एक्सपोर्ट करने का तरीका बताया गया है.

  • ऑब्जेक्ट प्रॉपर्टी को रेफ़र करने के लिए स्ट्रिंग के नामों का इस्तेमाल करना:

    कंपाइलर, ऐडवांस मोड में प्रॉपर्टी के नाम बदलता है. हालांकि, यह कभी भी स्ट्रिंग के नाम नहीं बदलता.

      var x = { renamed_property: 1 };
      var y = x.renamed_property; // This is OK.
    
      // 'renamed_property' below doesn't exist on x after renaming, so the
      //  following evaluates to false.
      if ( 'renamed_property' in x ) {}; // BAD
    
      // The following also fails:
      x['renamed_property']; // BAD

    अगर आपको कोट की गई स्ट्रिंग वाली किसी प्रॉपर्टी का रेफ़रंस देना है, तो हमेशा कोट की गई स्ट्रिंग का इस्तेमाल करें:

      var x = { 'unrenamed_property': 1 };
      x['unrenamed_property'];  // This is OK.
      if ( 'unrenamed_property' in x ) {};   // This is OK
  • ग्लोबल ऑब्जेक्ट की प्रॉपर्टी के तौर पर वैरिएबल का इस्तेमाल करना:

    कंपाइलर, प्रॉपर्टी और वैरिएबल के नाम अलग-अलग बदलता है. उदाहरण के लिए, कंपाइलर foo के इन दो रेफ़रंस को अलग-अलग मानता है, भले ही ये एक जैसे हों:

      var foo = {};
      window.foo; // BAD

    इस कोड को इस तरह से कंपाइल किया जा सकता है:

      var a = {};
      window.b;

    अगर आपको किसी वैरिएबल को ग्लोबल ऑब्जेक्ट की प्रॉपर्टी के तौर पर रेफ़र करना है, तो हमेशा उसे इस तरह रेफ़र करें:

    window.foo = {}
    window.foo;
    

Implications of dead code elimination

The ADVANCED_OPTIMIZATIONS compilation level removes code that is never executed. This elimination of dead code makes the following practices dangerous:

  • Calling functions from outside of compiled code:

    When you compile functions without compiling the code that calls those functions, the Compiler assumes that the functions are never called and removes them. To avoid unwanted code removal, either:

    • compile all the JavaScript for your application together, or
    • export compiled functions.

    Advanced Compilation and Externs describes both of these approaches in greater detail.

  • Retrieving functions through iteration over constructor or prototype properties:

    To determine whether a function is dead code, the Compiler has to find all the calls to that function. By iterating over the properties of a constructor or its prototype you can find and call methods, but the Compiler can't identify the specific functions called in this manner.

    For example, the following code causes unintended code removal:

    function Coordinate() {
    }
    Coordinate.prototype.initX = function() {
      this.x = 0;
    }
    Coordinate.prototype.initY = function() {
      this.y = 0;
    }
    var coord = new Coordinate();
    for (method in Coordinate.prototype) {
      Coordinate.prototype[method].call(coord); // BAD
    }
        

    कंपाइलर को यह समझ नहीं आता कि initX() और initY() को for लूप में कॉल किया गया है. इसलिए, वह इन दोनों तरीकों को हटा देता है.

    ध्यान दें कि अगर किसी फ़ंक्शन को पैरामीटर के तौर पर पास किया जाता है, तो कंपाइलर उस पैरामीटर के कॉल ढूंढ सकता है. उदाहरण के लिए, एडवांस मोड में इस कोड को कंपाइल करते समय, कंपाइलर getHello() फ़ंक्शन को नहीं हटाता.

    function alertF(f) {
      alert(f());
    }
    function getHello() {
      return 'hello';
    }
    // The Compiler figures out that this call to alertF also calls getHello().
    alertF(getHello); // This is OK.
        

ऑब्जेक्ट प्रॉपर्टी फ़्लैटनिंग के असर

ऐडवांस मोड में कंपाइलर, ऑब्जेक्ट प्रॉपर्टी को छोटा करता है, ताकि नाम छोटा किया जा सके. उदाहरण के लिए, कंपाइलर इसे बदल देता है:

   var foo = {};
   foo.bar = function (a) { alert(a) };
   foo.bar("hello");

में बदल दिया गया है:

   var foo$bar = function (a) { alert(a) };
   foo$bar("hello");

इस प्रॉपर्टी फ़्लैटनिंग की मदद से, बाद में नाम बदलने की प्रोसेस ज़्यादा आसानी से की जा सकती है. कंपाइलर, foo$bar को किसी एक वर्ण से बदल सकता है. उदाहरण के लिए.

हालांकि, प्रॉपर्टी फ़्लैटनिंग की वजह से, ये काम भी खतरनाक हो जाते हैं:

  • कंस्ट्रक्टर और प्रोटोटाइप मेथड के बाहर this का इस्तेमाल करना:

    प्रॉपर्टी फ़्लैटनिंग की वजह से, फ़ंक्शन में मौजूद कीवर्ड this का मतलब बदल सकता है. उदाहरण के लिए:

       var foo = {};
       foo.bar = function (a) { this.bad = a; }; // BAD
       foo.bar("hello");

    बन जाता है:

       var foo$bar = function (a) { this.bad = a; };
       foo$bar("hello");

    ट्रांसफ़ॉर्मेशन से पहले, this within foo.bar, foo को दिखाता है. ट्रांसफ़ॉर्मेशन के बाद, this का मतलब ग्लोबल this से है. इस तरह के मामलों में, कंपाइलर यह चेतावनी दिखाता है:

    "WARNING - dangerous use of this in static method foo.bar"

    प्रॉपर्टी फ़्लैटनिंग की वजह से, this के आपके रेफ़रंस काम करना बंद न करें, इसके लिए सिर्फ़ कंस्ट्रक्टर और प्रोटोटाइप के तरीकों में this का इस्तेमाल करें. this का मतलब तब साफ़ तौर पर समझ में आता है, जब new कीवर्ड का इस्तेमाल करके किसी कंस्ट्रक्टर को कॉल किया जाता है या prototype की प्रॉपर्टी वाले फ़ंक्शन में इसका इस्तेमाल किया जाता है.

  • यह जाने बिना स्टैटिक तरीकों का इस्तेमाल करना कि उन्हें किस क्लास में कॉल किया गया है:

    उदाहरण के लिए, निम्न स्थिति में:

    class A { static create() { return new A(); }};
    class B { static create() { return new B(); }};
    let cls = someCondition ? A : B;
    cls.create();
    कंपाइलर, ES6 से ES5 में ट्रांसपाइल करने के बाद, दोनों create तरीकों को छोटा कर देगा. इसलिए, cls.create() कॉल काम नहीं करेगा. @nocollapse एनोटेशन का इस्तेमाल करके, इस समस्या से बचा जा सकता है:
    class A {
      /** @nocollapse */
      static create() {
        return new A();
      }
    }
    class B {
      /** @nocollapse */
      static create() {
        return new A();
      }
    }
  • सुपरक्लास के बारे में जानकारी न होने पर, स्टैटिक तरीके से सुपर का इस्तेमाल करना:

    यह कोड सुरक्षित है, क्योंकि कंपाइलर को पता है कि super.sayHi(), Parent.sayHi() को रेफ़र करता है:

    class Parent {
      static sayHi() {
        alert('Parent says hi');
      }
    }
    class Child extends Parent {
      static sayHi() {
        super.sayHi();
      }
    }
    Child.sayHi();

    हालांकि, प्रॉपर्टी फ़्लैटनिंग की वजह से यहां दिया गया कोड काम नहीं करेगा. भले ही, myMixin(Parent).sayHi, Parent.sayHi uncompiled के बराबर हो:

    class Parent {
      static sayHi() {
        alert('Parent says hi');
      }
    }
    class Child extends myMixin(Parent) {
      static sayHi() {
        super.sayHi();
      }
    }
    Child.sayHi();

    /** @nocollapse */ एनोटेशन का इस्तेमाल करके, इस समस्या को ठीक करें.

  • Object.defineProperties या ES6 getter/setters का इस्तेमाल करना:

    कंपाइलर, इन कंस्ट्रक्ट को अच्छी तरह से नहीं समझता. ट्रांसपाइलेशन के ज़रिए ES6 गैटर और सेटर को Object.defineProperties(...) में बदल दिया जाता है. फ़िलहाल, कंपाइलर इस कंस्ट्रक्ट का स्टैटिक विश्लेषण नहीं कर सकता. साथ ही, यह मानता है कि प्रॉपर्टी ऐक्सेस करने और सेट करने से कोई साइड इफ़ेक्ट नहीं होता. इससे गंभीर नतीजे मिल सकते हैं. उदाहरण के लिए:

    class C {
      static get someProperty() {
        console.log("hello getters!");
      }
    }
    var unused = C.someProperty;

    इसे इस तरह से कंपाइल किया जाता है:

    C = function() {};
    Object.defineProperties(C, {a: // Note someProperty is also renamed to 'a'.
      {configurable:!0, enumerable:!0, get:function() {
        console.log("hello world");
        return 1;
    }}});

    C.someProperty से कोई साइड इफ़ेक्ट नहीं होता है. इसलिए, इसे हटा दिया गया है.