My new job is to write component-oriented JavaScript with Google Closure library. I adore events, components, services and modules. But the work is super-harsh because of the need to write code cluttered with namespaces. Following code is typical:
goog.provide(com.bin.slash.dot.closure.widget.SuperForm);
goog.require(com.bin.slash.dot.closure.widget.Avatar);
// ... ten require calls more...
com.bin.slash.dot.closure.widget.SuperForm = function() {
goog.base(this);
this._internal = new com.bin.slash.dot.closure.widget.Avatar(
com.bin.slash.dot.closure.widget.Avatar.SRC_PATH);
};
And I can't believe this is true. I am not afraid to type all of this, but I just feel that logic dissolved and cluttered in this symbol hell. It is very difficult to scan, hence it takes more time to understand what'is going on. My boss said, that it is discouraged to write shortcuts like:
var SF = com.bin.slash.dot.closure.widget.SuperForm = function(){};
Because all of them will be bound to the global namespace (window) after compilation, so they can interfere with something else.
The question is how to avoid this symbol hell?
Update: I have made an improvement into my developer process, which solves the symbol hell. Now I write sweetened JavaScript, that then automatically compiled by Grunt with sweet.js macros:
// For each file I define three macros which are replaced
// in the compile time with hell of a long paths.
macro dir { rule { $x } => { my.very.very.long.namespace $x } }
macro class { rule { $x } => { dir.NameOfMyClass $x } }
macro proto { rule { $x } => { class.prototype $x } }
dir.NameOfMyClass = function() {}; // yields: my.very.very.long.namespaceNameOfMyClass = function() {};
class.CONSTANT = "I don't know why we write constants into classes, not prototypes"; // yields: my.very.very.long.namespaceNameOfMyClass.CONSTANT = ...;
proto.method1 = function() {}; // yields my.very.very.long.namespaceNameOfMyClass.prototype.method1 = function(){};
All of the noise created by macro compiler is removed by excellent shelljs.
Assuming you're using the Closure Compiler, consider goog.scope
. There is built-in compiler support that replaces the aliased variables before optimization:
goog.scope
– a design document from the Closure Library wikigoog.scope
goog.scope
goog.provide
and goog.require
syntax is unaltered;Your code example, taking the above guide into consideration, could look somewhat like this:
goog.provide('my.very.long.namespace.NameOfMyClass');
goog.require('my.very.long.namespace');
/** @constructor */
my.very.long.namespace.NameOfMyClass = function() { /*...*/ };
goog.scope(function() {
var _ = my.very.long.namespace.NameOfMyClass;
_.CONSTANT = 'I don\'t know why we write constants into classes, not prototypes';
_.prototype.method1 = function() {};
}); // goog.scope
Since I don't have enough reputation to add a comment:
My boss said, that it is discouraged to write shortcuts like:
var SF = com.bin.slash.dot.closure.widget.SuperForm = function(){};
Because all of them will be bound to the global namespace (window) after compilation, so they can interfere with something else.
Even in the absence of those shortcuts, advanced compilation can rename symbols such as SF
or myVariable
to simply ga
. This can lead to clashes with external code such as Google Analytics.
The Closure-supported way to prevent such clashes on the global scope is introducing an immediately-invoked function expression after compilation (source). Use the compiler flag: --output_wrapper "(function(){%output%})();"
, or the strict mode compliant variation: --output_wrapper "(function(){%output%}).call(this);"
. When used, the shortcuts discouraged by your boss will be safe from collision with external symbols.