简体   繁体   中英

Preventing inlining of exported properties as constants w/ Google Closure Compiler

Here's a minimal example of input JavaScript (generated by another language, but that's neither here nor there):

goog.provide('foo.bar');
foo.bar.baz = 42;
goog.exportSymbol('foo.bar.baz', foo.bar.baz);
foo.bar.quux = (function quux(){return foo.bar.baz;
});
goog.exportSymbol('foo.bar.quux', foo.bar.quux);

My expectation is that, because foo.bar.baz is not annotated as a constant, that it would not be treated as one. Yet, advanced optimizations (both locally and via the compiler service ) inlines it anyway:

var d = this;
function f(g, e) {
  var b = g.split("."), a = d;
  b[0] in a || !a.execScript || a.execScript("var " + b[0]);
  for (var c;b.length && (c = b.shift());) {
    b.length || void 0 === e ? a = a[c] ? a[c] : a[c] = {} : a[c] = e;
  }
}
;f("foo.bar.baz", 42);
f("foo.bar.quux", function() {
  return 42;
});

Constant inlining is great, but the value in question isn't safe to inline, given that it's exported. I've tried using goog.exportProperty and @expose annotations in addition to and instead of goog.exportSymbol , with no luck.

Help? Thanks!

Update: @expose is now deprecated. Answer updated with new method.

As you noted, @const has no impact on inlining. @const only means that the value doesn't change (is assigned/set only once).

@nocollapse was specifically created to prevent property collapsing. Given:

goog.provide('foo.bar');
/** @nocollapse */
foo.bar.baz = 42;
alert(foo.bar.baz);

The purpose of @nocollapse in this case is to prevent the compiler from collapsing the property to something like:

var a = 42;
alert(a);

However the compiler still knows from flow analysis that foo.bar.baz is assigned the integer 42 and never changed. Therefore it's going to just use the known values.

The only reliable way to prevent inlining is to export the property:

goog.provide('foo');
goog.provide('foo.bar');
foo.bar.baz = 42;
goog.exportProperty(foo.bar, 'baz', foo.bar.baz);
alert(foo.bar.baz);

There is a known (albeit small) need to stipulate to the compiler that renaming is safe, but inlining is not. See https://code.google.com/p/closure-compiler/issues/detail?id=971

If you are not using closure library, the export would look like this:

var foo = {};
foo.bar = {};
/** @nocollapse */
foo.bar.baz = 42;
foo.bar['baz'] = foo.bar.baz;
alert(foo.bar.baz);

If your goal is to make sure foo.bar.baz isn't inlined OR renamed, then you would need either quoted properties and @nocollapse .

goog.provide('foo');
goog.provide('foo.bar');
/** @nocollapse */
foo.bar.baz = 42;
goog.exportProperty(foo.bar, 'baz', foo.bar.baz);
alert(foo.bar.baz);

If you are providing this namespace to others for use, then the best case is a combination of @nocollapse to prevent collapsing and allow external updating and goog.exportSymbol .

goog.provide('foo');
goog.provide('foo.bar');
/** @nocollapse */
foo.bar.baz = 42;
goog.exportSymbol('foo', foo);
goog.exportProperty(foo, 'bar', foo.bar);
goog.exportProperty(foo.bar, 'baz', foo.bar.baz); 

alert(foo.bar.baz);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM