Module pattern with Closure Compiler and ADVANCED_OPTIMIZATIONS - javascript

I am aware that similar questions have been asked before, but methodology changes quickly so I'm seeking to understand current best practices. (In fact, as recently as 2 days ago, Chad Killingsworth added a comment to an accepted answer from three years ago that #expose annotation is now deprecated.)
I'm using the module pattern. 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 #expose annotation, above can be minified with Closure in advance mode and the following calls work (minified 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 away from the module pattern, I would like to update code (approx 10,000 lines) to use #export annotation. However, when I replace #expose with #export Closure raises 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 am aware that I can possibly 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. Further JSLint complains about weird assignments so I would rather use JSDocs annotation.

Until the issue raised by #ChadKillingsworth is resolved, here's a solution which will enable you to use #export with only minor modifications to 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;
};
});
The changes are:
Change the #expose tags to #export.
Create an empty MATHCALCS object outside the module wrapper function, and make the MY alias point to it.
Instead of executing the module wrapper function immediately (IIFE), pass it to goog.scope(). This enables aliasing within scope functions, allowing the compiler to work out that the exported symbols are being defined on the global MATHCALCS object. This prevents the compiler from raising the error ("#export only applies to symbols/properties defined in the global scope").
Remove the following items, which are not needed:
The #export tags on this.color and this.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 g() function is the compiled version of goog.exportSymbol() – see the #export docs for more details.
Note: if you want to run the code uncompiled, you'll need to load the Closure Library, or define goog.scope() yourself:
var goog = {};
goog.scope = function(fn) {
fn();
};
Here's a fork of your JSFiddle with all these changes.

Related

JSDoc: Is conditional ignore possible?

Is there a way to ignore some documented symbols conditionally?
I'd like to do something like this (pseudo-code):
/**
* #ignore if dev
*/
var a = 42;
/**
* #ignore if prod
*/
var b = 24;
In this example I would like to have my JSDoc generator to only document var a if I configured my generator to dev and vice versa for var b.
Is this possible?
You can implement your own jsdoc plugin to test an ignore condition and set its value in doclet.ignore property. Setting it to true will prevent the doclet being processed - not adding it to the final documentation.
exports.defineTags = function(dictionary) {
var env = require('jsdoc/env');
/*
* Usage: #customIgnore prod, test
*/
dictionary.defineTag('customIgnore', {
mustHaveValue: true,
onTagged: function(doclet, tag) {
var i;
// "envParams" is a property of your jsdoc.json
var environments = env.conf.envParams;
// Will hold "prod, test"
var tagValue = tag.value;
var conditionValues = tag.value.split(",");
for (i = 0; i < conditionValues.length && !doclet.ignore; i++) {
if (environments.indexOf(conditionValues[i].trim()) !== -1) {
doclet.ignore = true;
}
}
}
});
};

Javascript: 'Protected' Scoping

How does one implement 'protected variables' in javascript?
var DIM = {
makeOneDim: function(x) {
var magic = Math.random();
return {
dist: function() {
return x * magic;
}
};
},
makeTwoDim: function(x,y) {
var o = DIM.makeOneDim(x);
o.dist = function() {
magic *= Math.random();
return Math.sqrt(x*x+y*y) * magic;
// !! cant access 'private' variable magic !!
}
return o;
}
};
var o = DIM.makeOneDim(123);
var oo = DIM.makeTwoDim(1,2);
console.log(o.dist() + oo.dist());
I know in this example it would be possible to make a 'public getter', but i would still want to mutate the magic variable from within makeTwoDim's scope and hide it from its instances.
It would also be nice if I could somehow prevent recreating the dist functions on each creation of an instance.
Does this suit your needs? It uses a revealing module pattern to make magic private and return the functions. As such there is no need to call makeOneDim from makeTwoDim.
var DIM = (function () {
var magic = Math.random();
function makeOneDim(x) {
return {
dist: function() {
return x * magic;
}
};
}
function makeTwoDim(x, y) {
return {
dist: function() {
magic *= Math.random();
return Math.sqrt(x * x + y * y) * magic;
}
}
}
return {
makeOneDim: makeOneDim,
makeTwoDim: makeTwoDim
}
});
var o = DIM().makeOneDim(123);
var oo = DIM().makeTwoDim(1,2);
DEMO
There are a lot of ways you could engineer this, but the simplest would be to just keep the functions together:
var DIM = {
makeOneDim: function(x) {
var magic = Math.random();
var dist = function() {
return x * magic;
};
var dist2 = function(y, y) {
magic *= Math.random();
return Math.sqrt(x*x+y*y) * magic;
};
return {
dist : dist,
dist2 : dist2,
};
}
};
(and, did you really mean to change the value of magic with each call?)
You should be able to determine the value of magic within makeTwoDim by dividing o.dist() by x
var o = DIM.makeOneDim(x);
var magic = o.dist() / x;
console.log(magic)
You need to make environment with scope function.
The jQuery way :
var myClass;
(function () {
var a = 0; // private var
function protect_function() {
//...
}
window.myClass = {
public_function: function () { }
};
})();
I know I'm likely to collect rotten tomatoes, but I'd like to say that I disagree with the use of "protected" or "private" variables in the scope of JavaScript OOP. There are no builtin safeguards in JavaScript, developpers are doomed to be better than the others, that's it :-D Don't try to mimic Java, just take it as an opportunity to improve your programming skills. Thus, this code looks perfect to me:
AClass = function () {
// `rdm` should never change
this.rdm = Math.random();
};

Unknown provider for $httpParamSerializer

I came across an Angular.js service named $httpParamSerializer and thought it could be useful for my code. However, when I tried to inject it Angular.js didn't recognize it and threw an "unknown provider" error.
Isn't $httpParamSerializer a built-in service (just like $http)? Why is this happening?
I have no problem injecting other built-in services such as $http, $httpBackend etc.
Thanks.
It was just recently added in Angular v1.4.0-rc.0. - so, check and fix your version of Angular:
<script src="https://code.angularjs.org/1.4.0-rc.0/angular.js">
I needed this also, but at the moment we cannot upgrade, so I just took code and created provider and added it to my "common" module. Once we upgrade, I'll just remove it.
(function (angular) {
'use strict';
var serviceId = '$httpParamSerializer';
var common = angular.module('common');
common.provider(serviceId, $HttpParamSerializerProvider);
function $HttpParamSerializerProvider() {
function sortedKeys(obj) {
return Object.keys(obj).sort();
}
function forEachSorted(obj, iterator, context) {
var keys = sortedKeys(obj);
for (var i = 0; i < keys.length; i++) {
iterator.call(context, obj[keys[i]], keys[i]);
}
return keys;
}
function encodeUriQuery(val, pctEncodeSpaces) {
return encodeURIComponent(val).
replace(/%40/gi, '#').
replace(/%3A/gi, ':').
replace(/%24/g, '$').
replace(/%2C/gi, ',').
replace(/%3B/gi, ';').
replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
}
function serializeValue(v) {
if (isObject(v)) {
return isDate(v) ? v.toISOString() : toJson(v);
}
return v;
}
function isUndefined(value) { return typeof value === 'undefined'; }
var isArray = Array.isArray;
function isObject(value) {
// http://jsperf.com/isobject4
return value !== null && typeof value === 'object';
}
/**
* #ngdoc service
* #name $httpParamSerializer
* #description
*
* Default {#link $http `$http`} params serializer that converts objects to strings
* according to the following rules:
*
* * `{'foo': 'bar'}` results in `foo=bar`
* * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object)
* * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element)
* * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D"` (stringified and encoded representation of an object)
*
* Note that serializer will sort the request parameters alphabetically.
* */
this.$get = function() {
return function ngParamSerializer(params) {
if (!params) return '';
var parts = [];
forEachSorted(params, function(value, key) {
if (value === null || isUndefined(value)) return;
if (isArray(value)) {
forEach(value, function(v, k) {
parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v)));
});
} else {
parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value)));
}
});
return parts.join('&');
};
};
}
}(angular))

Should I create private variables/methods in this manner?

I am trying to learn more about how to create private variables and methods in JavaScript. The code below seems to work but I feel as though there may be a more efficient way to do this. Any suggestions?
var CountObject = (function () {
function countObject() {
// private variables
var _a = 1;
var _b = 2;
var _c = _a + _b;
// private method
addTo = function (num) {
_c = _c + _a + _b + num;
return _c;
}
}
// public method
countObject.prototype.add = function (num) {
return addTo(num);
};
return countObject;
}());
var testObject1 = new CountObject();
console.log(testObject1.add(1));
//output 7
console.log(testObject1.add(1));
//output 11
console.log(testObject1.add(1));
//output 15
var testObject2 = new CountObject();
console.log("testobject2:" + testObject2.add(100));
//output testobject2:106
addTo is not a private (local) method, but a global (not even instance-specific) function! Try to do testObject1.add(0) again after having created the testObject2 and you will see it fail.
You cannot call local (constructor-scoped) functions from the prototype, so this seems pointless. Fix it by using an instance-specific, privileged (with access to the local variables) method:
function CountObject() {
// private variables
var a = 1;
var b = 2;
var c = a + b;
// public method
this.add = function (num) {
c = c + a + b + num;
return c;
};
}

mocha cannot create an instance of my function

Just starting out with mocha and cannot for the life of me figure out why it thinks Helper is undefined at the indicated line/columns below:
test.js
var assert = require('assert'),
helper = require('../src/js/helper.js');
describe('helper', function() {
describe('#parseValue', function() {
it('should return number of minutes for a properly formatted string', function() {
assert.equal(1501, (new Helper()).parseValue('1d 1h 1m', 'when'));
^^^^^^^^^^^^
});
});
});
helper.js
(function(exports) {
'use strict';
function Helper(opts) {
this.opts = opts || {};
/**
* Parse a value based on its type and return a sortable version of the original value
*
* #param {string} val input value
* #param {string} type type of input value
* #returns {mixed} sortable value corresponding to the input value
*/
this.parseValue = function(val, type) {
switch (type) {
case 'when':
var d = val.match(/\d+(?=d)/),
h = val.match(/\d+(?=h)/),
m = val.match(/\d+(?=m)/);
if (m)
m = parseInt(m, 10);
if (h)
m += parseInt(h, 10) * 60;
if (d)
m += parseInt(d, 10) * 1440;
val = m;
break;
default:
break;
}
return val;
};
}
exports.helper = Helper;
})(this);
I wrote a quick test in the browser without mocha to ensure my helper.js functions were accessible and it worked fine, so I really am at a loss. I am running this directly on my server by calling mocha from the command line in my directory.
You never define Helper in test.js—only helper on this line:
var helper = require('../src/js/helper.js');
Use the lower case helper that you defined.
By the way, you might want to change your exports line in helper.js from this:
exports.helper = Helper;
To this:
exports.Helper = Helper;
Then use helper in test.js like so:
assert.equal(1501, (new helper.Helper()).parseValue('1d 1h 1m', 'when'));
Or just do something like this:
var Helper = require('../src/js/helper.js').Helper;

Categories