My new job is to write ponent-oriented JavaScript with Google Closure library. I adore events, ponents, 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(.bin.slash.dot.closure.widget.SuperForm);
goog.require(.bin.slash.dot.closure.widget.Avatar);
// ... ten require calls more...
.bin.slash.dot.closure.widget.SuperForm = function() {
goog.base(this);
this._internal = new .bin.slash.dot.closure.widget.Avatar(
.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 = .bin.slash.dot.closure.widget.SuperForm = function(){};
Because all of them will be bound to the global namespace (window) after pilation, 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 piled by Grunt with sweet.js macros:
// For each file I define three macros which are replaced
// in the pile 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 piler is removed by excellent shelljs.
My new job is to write ponent-oriented JavaScript with Google Closure library. I adore events, ponents, 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(.bin.slash.dot.closure.widget.SuperForm);
goog.require(.bin.slash.dot.closure.widget.Avatar);
// ... ten require calls more...
.bin.slash.dot.closure.widget.SuperForm = function() {
goog.base(this);
this._internal = new .bin.slash.dot.closure.widget.Avatar(
.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 = .bin.slash.dot.closure.widget.SuperForm = function(){};
Because all of them will be bound to the global namespace (window) after pilation, 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 piled by Grunt with sweet.js macros:
// For each file I define three macros which are replaced
// in the pile 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 piler is removed by excellent shelljs.
Share Improve this question edited May 20, 2018 at 20:15 peterh 1 asked Jan 14, 2014 at 5:41 Light-wharfLight-wharf 1471 silver badge7 bronze badges 3- 3 I'm not sure I see your question here. – Draculater Commented Jan 14, 2014 at 5:43
- 2 Just define local variables instead of global variables? – user229044 ♦ Commented Jan 14, 2014 at 5:49
-
As to: "I don't know why we write constants into classes, not prototypes" – one reason to do it is that the piler can more efficiently rename it to
a
instead ofthis.a
in advanced mode. There might be more important reasons. – mesteiral Commented Apr 20, 2014 at 22:16
3 Answers
Reset to default 6Assuming you're using the Closure Compiler, consider goog.scope
. There is built-in piler support that replaces the aliased variables before optimization:
- Making Closure Library less verbose with
goog.scope
– a design document from the Closure Library wiki - Google JavaScript Style Guide: Aliasing with
goog.scope
- Documentation for
goog.scope
- Source code example from Closure Library code
In a gist
goog.provide
andgoog.require
syntax is unaltered;- you can start declaring non-constructor namespaces inside the scope;
- constructor namespaces need to be declared outside the scope that renames them.
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
Extra
Since I don't have enough reputation to add a ment:
My boss said, that it is discouraged to write shortcuts like:
var SF = .bin.slash.dot.closure.widget.SuperForm = function(){};
Because all of them will be bound to the global namespace (window) after pilation, so they can interfere with something else.
Even in the absence of those shortcuts, advanced pilation 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 pilation (source). Use the piler flag: --output_wrapper "(function(){%output%})();"
, or the strict mode pliant variation: --output_wrapper "(function(){%output%}).call(this);"
. When used, the shortcuts discouraged by your boss will be safe from collision with external symbols.
There is nothing wrong with defining references to simplify your workflow IMO.
var
widget = .bin.slash.dot.closure.widget
widget.methA = function(){ widget.propertyA = 10}
Namespacing serves a purpose and while I can't speak on your codebase, chances are there are better ways to organize this library.
What about creating separate local context using immediate function:
(function () {
var SF = .bin.slash.dot.closure.widget.SuperForm = function(){};
}());
Your code should be put only inside this function. This way you will never overwrite any global variable.