CodeYogi
CodeYogi

Reputation: 1412

Namespace issue in google closure library

In the tutorial mentioned here, the namespace provided by the module is:

goog.provide('tutorial.notepad.Note');

But I am wondering why not this:

goog.provide('tutorial.notepad');

Since, according to the rule mentioned below:

tutorial = tutorial || {};
tutorial.notepad = tutorial.notepad || {};
tutorial.notepad.Note = tutorial.notepad.Note || {};

If we just provided:

goog.provide('tutorial.notepad'); then, we would already have:

tutorial = tutorial || {};
tutorial.notepad = tutorial.notepad || {};

to which we could have added property Note

tutorial.notepad.Note = function() {};

Hence, my question is:

Why not just declare goog.provide('tutorial.notepad') and then use that to include top level Classes, instead its recommended to use goog.provide('tutorial.notepad.Note') for each Class which feels redundant to me.

Upvotes: 2

Views: 835

Answers (2)

owler
owler

Reputation: 1089

Having goog.provide('tutorial.notepad'); creates an entry in the "tree of dependencies" for that namespace, but it does not create an entry for the class tutorial.notepad.Note. If you manually create tutorial.notepad.Note as in your example code then you don't activate closure-compiler mechanisms to include the class tutorial.notepad.Note into the tree of namespace dependencies that closure-compiler uses.

The reason is that goog.provide is used by closure compiler to set up the tree of dependencies used to figure out what namespaces to load and in what order.

By not using goog.provide, but mimicking its effects with the code you show, the compiler doesn't learn about the class Note and how it fits into the tree of namespaces and classes and their dependencies.

There are two ways to run closure-compiler based code: compiled and uncompiled. Each of these build and use the tree of namespace dependencies differently:

  • UNCOMPILED One of the great things about closure-compiler is that you can run all your code uncompiled. A necessary step in that process is to use depswriter.py, a Python program which reads all your source files (looking for goog.provide and goog.require calls) and produces a file deps.js. That deps.js file is the embodiment of the namespace dependency tree. Here is one sample line (of 333) from my project's deps.js file:

    goog.addDependency('../../../src/lab/app/ViewPanner.js',
      ['myphysicslab.lab.app.ViewPanner'], ['myphysicslab.lab.util.DoubleRect',
       'myphysicslab.lab.util.UtilityCore', 'myphysicslab.lab.util.Vector',
       'myphysicslab.lab.view.CoordMap', 'myphysicslab.lab.view.LabView'], false);
    

When I run my code in the uncompiled state, there is a <script> tag that runs that deps.js script. Doing that causes an in-memory version of the namespace dependency tree to be created which is accessed by goog.require at run-time to load whatever other files are needed for that particular class.

  • COMPILED The compiler (a Java program) does much the same thing as described above as part of the compilation process. The difference is that the resulting tree of namespace dependencies is only used during compilation to figure how what order to define classes in, to figure out what is needed, etc. The tree of namespace dependencies is discarded when compilation is finished.

References:

https://github.com/google/closure-compiler/wiki/Managing-Dependencies

https://github.com/google/closure-compiler/wiki/Debugging-Uncompiled-Source-Code


Responding to your comment:

Why not just declare goog.provide('tutorial.notepad') and then use that to include top level Classes, instead its recommended to use goog.provide('tutorial.notepad.Note') for each Class which feels redundant to me.

I think this gets into issues about the goals and design of closure-compiler. As @Technetium points out, using closure-compiler "is extremely verbose" - it requires annotating your JavaScript code with comments to tell what are the input and output types of every method (function) and the type of each property of an object (class).

(I'm no compiler expert but) I think doing what you suggest would require the compiler to "understand" your code and make guesses about what you regard to be a class, and what you regard to be the constructor and methods or other properties of that class. This would be a much harder problem than what the closure-compiler designers arrived at - especially because JavaScript is such a "loose" language which allows you to do almost anything you can think of.

In practice I find the goog.provide to be not at all troublesome. I usually am defining only one class per file. What I find much more of a bother is all the goog.require statements. I can often have 20 or 30 of these in a file and this list of files is often repeated in a similar class. I have 3870 occurrences of goog.require in my code.

Even this would be OK, but what makes it worse is that closure-compiler has a goog.scope mechanism which lets you use shorter names, like I can then say Vector instead of new myphysicslab.lab.util.Vector. That's very nice, but the problem is that each class you've already goog.required you then have to make a short variable within the goog.scope with a line like this:

var Vector = myphysicslab.lab.util.Vector;

Anyway, my point is: yes, closure-compiler requires a lot more code than raw JavaScript. But the goog.provide is the least of the issues in that regard.

One more thing: user @Technetium states

The real reason to use it is to run your Google Closure code through the javascript-to-javascript Closure Compiler that removes dead/unused code while minimizing and obfuscating the pieces you do use.

While that's an incredibly useful feature, there is another hugely important reason to use closure-compiler: type checking. If you take the time to add the annotations to your functions, then the compiler will "have your back" by catching errors. This is a big help on any project, but becomes critical when you have multiple developers working on a project and is one of the main reasons that Google developed closure compiler.

Upvotes: 2

Technetium
Technetium

Reputation: 6158

A couple things at play here:

  1. You can only evoke goog.provide() once per namespace.

You may currently have your "class" defined in a single file, say Note.js, with goog.provide('tutorial.notepad'); right now. However, if you add another file, say Tab.js, that has the "class" tutorial.notepad.Tab in it, you're going to run into this error when Tab.js also calls goog.provide('tutorial.nodepad').

  1. Calling goog.provide('tutorial.notepad') does not tell the Closure Compiler about the "class" tutorial.notepad.Note

Google Closure code is extremely verbose in its raw library form. The real reason to use it is to run your Google Closure code through the javascript-to-javascript Closure Compiler that removes dead/unused code while minimizing and obfuscating the pieces you do use. While your example works in debug mode since it does not leverage the Closure Compiler, once the Closure Compiler is ran and tries to build a dependency map it will fail to find the tutorial.notepad.Note class when something tries to reference it via goog.requires('tutorial.notepad.Note'). If you want to learn more about how this dependency map works, owler's answer is a very good starting place.

As an aside, note that I use "class" in quotes, and quite intentionally. While Google Closure gives the look and feel of object oriented programming in many ways with its @constructor annotation, and a rough analog of package/import via goog.provide/goog.require syntax, it is still JavaScript at the end of the day.

Upvotes: 1

Related Questions