Module schema with Closure compiler and ADVANCED_OPTIMIZATIONS

I know similar questions have been asked before, but the methodology is changing rapidly, so I am trying to understand current best practices. (In fact, as early as 2 days ago, Chad Killingsworth added a comment to the accepted answer from 3 years ago that the annotation is @expose

now out of date.)

I am using the module template . Working JSFIDDLE of the below code:

/** @const */
var MATHCALCS = (function () {
    'use strict';

    var MY = {};

    /**
     * @constructor
     * @param {!Object} obj
     * @expose
     */
    MY.ModuleStruct = function (obj) {
        /** @expose */
        this.color = (obj.color !== undefined) ? obj.color : null;
        /** @expose */
        this.size = (obj.size !== undefined) ? obj.size : null;
    };

    /**
     * @expose
     */
    MY.ModuleStruct.prototype.clone = function () {
        return new MY.ModuleStruct({
            "color": this.color,
                "size": this.size
        });
    };


    MY.moduleProperty = 1;

    /**
     * @type {function(!Array<number>)}
     * @expose
     */
    MY.moduleMethod = function (a) {
        var i, x = 0;
        for (i = 0; i < a.length; i += 1) {
            x = x + a[i];
        }
        return x;
    };

    return MY;

}());

window["MATHCALCS"] = MATHCALCS;*

      

Currently, using the annotation @expose

above, it is possible to minify with the close function in forward mode and the following calls work ( example example ):

// call a public method
alert(MATHCALCS.moduleMethod([1, 2, 3]));

// allocate a new structure
var ms = new MATHCALCS.ModuleStruct({
    "color": "red",
        "size": "small"
});
alert(ms.color + '\t' + ms.size);

// clone a second instance
var ms2 = ms.clone();
alert(ms2.color + '\t' + ms2.size);
alert(ms !== ms2); // cloned objs are not equal

// and directly update the properties of the object
ms2.color = "white";
ms2.size = "large";
alert(ms2.color + '\t' + ms2.size);

      

If possible, without changing the module template, I would like to update the code (approximately 10,000 lines) to use the annotation @export

. However, when I replace @expose

with @export

, Closure throws this error:

ERROR - @export only applies to symbols / properties defined in the global scope.

Q: Is it possible, and if so, how should the above code be annotated to work with ADVANCED_OPTIMIZATIONS?

I know that I can use this type of notation:

MY["ModuleStruct"] = MY.ModuleStruct;
MY["ModuleStruct"]["prototype"]["clone"] = MY.ModuleStruct.prototype.clone;

      

but exporting object properties this way will become tedious. Next JSLint complains about weird assignments, so I would rather use the JSDocs annotation.

+3


source to share


1 answer


Before the problem posed by @ChadKillingsworth is resolved, here is a solution that will allow you to use @export

with only minor changes in your code:

/** @const */
var MATHCALCS = {};

goog.scope(function () {
    'use strict';

    var MY = MATHCALCS;

    /**
     * @constructor
     * @param {!Object} obj
     * @export
     */
    MY.ModuleStruct = function (obj) {
        this.color = (obj.color !== undefined) ? obj.color : null;
        this.size = (obj.size !== undefined) ? obj.size : null;
    };

    /**
     * @export
     */
    MY.ModuleStruct.prototype.clone = function () {
        return new MY.ModuleStruct({
            "color": this.color,
                "size": this.size
        });
    };


    MY.moduleProperty = 1;

    /**
     * @type {function(!Array<number>)}
     * @export
     */
    MY.moduleMethod = function (a) {
        var i, x = 0;
        for (i = 0; i < a.length; i += 1) {
            x = x + a[i];
        }
        return x;
    };

});

      

Changes:

  • Change the tags @expose

    to @export

    .
  • Create an empty object MATHCALCS

    outside the module wrapper function and alias it MY

    .
  • Instead of immediately executing the module wrapper function (IIFE), go to goog.scope()

    . This allows the scope functions to be flattened, allowing the compiler to determine that the exported symbols are defined by the global object MATHCALCS

    . This prevents the compiler from collecting an error (" @export

    only applies to symbols / properties defined in the global scope").
  • Remove the following items that are not needed:
    • Tags @export

      on this.color

      andthis.size

    • return MY;

    • window["MATHCALCS"] = MATHCALCS;

When compiled with this command:

java -jar compiler.jar \
    --js closure/goog/base.js \
    --js mathcalcs.js \
    --js_output_file mathcalcs.min.js \
    --compilation_level ADVANCED_OPTIMIZATIONS \
    --generate_exports \
    --formatting PRETTY_PRINT \
    --output_wrapper '(function() {%output%}).call(window);'

      



You'll get:

(function() {var f = this;
function g(a, d) {
  var b = a.split("."), c = f;
  b[0] in c || !c.execScript || c.execScript("var " + b[0]);
  for (var e;b.length && (e = b.shift());) {
    b.length || void 0 === d ? c[e] ? c = c[e] : c = c[e] = {} : c[e] = d;
  }
}
;function h(a) {
  this.color = void 0 !== a.color ? a.color : null;
  this.size = void 0 !== a.size ? a.size : null;
}
g("MATHCALCS.ModuleStruct", h);
h.prototype.clone = function() {
  return new h({color:this.color, size:this.size});
};
h.prototype.clone = h.prototype.clone;
g("MATHCALCS.moduleMethod", function(a) {
  var d, b = 0;
  for (d = 0;d < a.length;d += 1) {
    b += a[d];
  }
  return b;
});
}).call(window);

      

The function g()

is the compiled version goog.exportSymbol()

- see the @export

docs
for more details.

Note. If you want to run the code without compiling, you need to load the closure library or define yourself goog.scope()

:

var goog = {};
goog.scope = function(fn) {
    fn();
};

      

Here's a fork of your JSFiddle with all of these changes.

0


source







All Articles