3

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.

peterh
  • 11,875
  • 18
  • 85
  • 108
Light-wharf
  • 147
  • 1
  • 7
  • 3
    I'm not sure I see your question here. – Draculater Jan 14 '14 at 05:43
  • 2
    Just define *local* variables instead of global variables? – user229044 Jan 14 '14 at 05:49
  • As to: "I don't know why we write constants into classes, not prototypes" – one reason to do it is that the compiler can more efficiently rename it to `a` instead of `this.a` in advanced mode. There might be more important reasons. – mesteiral Apr 20 '14 at 22:16

3 Answers3

6

Assuming you're using the Closure Compiler, consider goog.scope. There is built-in compiler support that replaces the aliased variables before optimization:

In a gist

  • goog.provide and goog.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 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.

TachyonVortex
  • 8,242
  • 3
  • 48
  • 63
mesteiral
  • 507
  • 3
  • 9
1

What about creating separate local context using immediate function:

(function () {
    var SF = com.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.

Karol
  • 7,803
  • 9
  • 49
  • 67
  • Yeah, safety wrapper is a good idea, but is that appropriate for the Closure Compiler in advanced mode? Will test today. – Light-wharf Jan 14 '14 at 06:11
  • I don't think it will get rid of immediate function, but waiting for your test then. – Karol Jan 14 '14 at 06:18
  • I hate to say this, but I didn't made the tests, sorry. But I found a solution, which I like more, and posted it into original question. – Light-wharf Jan 23 '14 at 14:47
  • That's ok - usually there is more than one solution, and you choose whatever works best for you. Also you should rather post it as an answer, and accept it, so the question will not remain unanswered. – Karol Jan 23 '14 at 22:31
  • 2
    @overmind1 A wrapper function like this is indeed important, to hide the alias from the global scope. But for the Closure Compiler to correctly process the alias, the wrapper function must be passed to `goog.scope()`. See [mesteiral's answer](http://stackoverflow.com/a/21471276/1851186). Also see [this documentation in the Compiler source code](https://github.com/google/closure-compiler/blob/v20150505/src/com/google/javascript/jscomp/ScopedAliases.java#L42) for a good description. – TachyonVortex May 22 '15 at 13:59
1

There is nothing wrong with defining references to simplify your workflow IMO.

var 
widget = com.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.

Draculater
  • 2,280
  • 1
  • 24
  • 29
  • This is how we do it at my company when we get atypically long namespaces. If you define the namespaces as references near the top of a function, it allows the eyes to gloss over the namespace of the function and namespace references in a single, logical chunk, letting you focus on the the rest of the function that contains the core logic. – Technetium Jan 14 '14 at 17:03
  • 1
    @Technetium this is good approach indeed, but if you don't encapsulate it in some local scope, you will overwrite `widget` property of a `window` object which can cause problems. – Karol Jan 16 '14 at 00:46
  • 2
    Please fix this code so you're not creating global variables with the aliasing – Ruan Mendes Mar 13 '14 at 01:25