Proposal: goog.amend()

76 views
Skip to first unread message

Michael Bolin

unread,
Oct 26, 2011, 11:31:17 AM10/26/11
to Closure Library Discuss
I would like to propose goog.amend(), which would signify that you are
amending a namespace. It is different than goog.require(), which
indicates that the required dependency must be loaded _anywhere_
before the current file whereas goog.amend() would indicate that the
required dependency be _immediately_ loaded before the current file.
Or, thought of another way, the current file amends the specified
file.

goog.amend() takes one optional argument. If specified, it must be a
string literal that identifies a namespace (such as
'goog.ui.Component'). If no argument is specified, the file will be
immediately loaded after base.js (where base.js is defined as the
unique file that contains the "// Identifies this file as the Closure
base." line).

Use case #1: goog.setCssNameMapping()

If you programmatically generate a CSS name mapping (which is a JS
file that calls goog.setCssNameMapping() with an object literal), then
you want to be sure that it is immediately loaded after base.js so
that all calls to goog.getCssName() in your application are guaranteed
to use the mapping.

Use case #2: Amend goog.ui.Component

Suppose you have developed a large application and you realize that
you wish that all of your components had a particular method.
Unfortunately, you did not create your own subclass of
goog.ui.Component for your application and you do not want to modify
component.js because then you have to maintain your patches to it as
you update your copy of the Closure Library going forward. It would be
cleaner to create your own file that uses
goog.amend('goog.ui.Component') and adds things to
goog.ui.Component.prototype. Personally, I have wanted to do this for
both goog.ui.Component and goog.async.Deferred.

Files that declare goog.amend() may also contain goog.provide() and
goog.require() so that multiple files that goog.amend() the same
namespace can order themselves.

Admittedly, implementing this feature would require changes to both
the Closure Library and Closure Compiler, but I am willing to do the
work if the team is on board.

Thoughts?
Michael

Nick Santos

unread,
Oct 26, 2011, 12:09:45 PM10/26/11
to closure-lib...@googlegroups.com
>
> Use case #2: Amend goog.ui.Component
>
> Suppose you have developed a large application and you realize that
> you wish that all of your components had a particular method.
> Unfortunately, you did not create your own subclass of
> goog.ui.Component for your application and you do not want to modify
> component.js because then you have to maintain your patches to it as
> you update your copy of the Closure Library going forward. It would be
> cleaner to create your own file that uses
> goog.amend('goog.ui.Component') and adds things to
> goog.ui.Component.prototype. Personally, I have wanted to do this for
> both goog.ui.Component and goog.async.Deferred.

suppose you goog.require goog.ui.Component. who decides whether you
get the amend or not?

Michael Davidson

unread,
Oct 26, 2011, 12:24:15 PM10/26/11
to closure-lib...@googlegroups.com
This doesn't help the outside world (yet!), but have you looked at the mod system that Gmail uses? Seems to meet many of the same needs, and is already supported by the compiler. 

Michael

Michael Bolin

unread,
Oct 26, 2011, 12:28:39 PM10/26/11
to Closure Library Discuss
You decide by including the file witht he goog.amend() in your list of
"paths" (to use the closurebuilder.py term).

Michael Bolin

unread,
Oct 26, 2011, 12:29:19 PM10/26/11
to Closure Library Discuss
I haven't looked, but from what I remember, I don't believe the mods
system helps with use case #1, does it?

Michael Bolin

unread,
Oct 26, 2011, 12:31:59 PM10/26/11
to Closure Library Discuss
Also, I thought the Gmail build system required you to list your
dependencies and mods and things explicitly, whereas I would like the
dependency information to be in the code in a machine-processable way.
This is what makes it possible for tools like plovr to infer a module
graph rather than placing that burden on the developer.

Michael Davidson

unread,
Oct 26, 2011, 12:46:34 PM10/26/11
to closure-lib...@googlegroups.com
I feel bad discussing this on an external list where I don't have time to fully explain how Gmail works, but we do put things in the code. Search for @mod and @modName.

You're right about use case #1, but don't people typically call things like getCssName() after all of the JS has been evaluated? Seems safest to not call that method statically during evaluation. 

Michael

Michael Bolin

unread,
Oct 26, 2011, 1:00:11 PM10/26/11
to Closure Library Discuss
I'm concerned about the case where someone does:

MyComponent.prototype.BASE_CLASS = goog.getCssName('my-component');

I don't think that someone should have to worry about being bitten by
this. There is nothing to worry about if goog.setCssNameMapping() is
loaded immediately after base.js.

I can see similar situations for a generated translation file or other
global constants in JS that are generated (say shared constants from a
Java frontend and a JS client). It's just simpler if they are
guaranteed to be loaded after base.js but before everything else
because to the application developer, these things should be part of
the bootstrapping as base.js is.

If you really believe that @mod or whatever Gmail does is the right
thing to do for case #2, then maybe there should be a special way to
inject bootstrapping files for case #1.

David Turnbull

unread,
Oct 26, 2011, 2:14:22 PM10/26/11
to closure-lib...@googlegroups.com
My concern with goog.amend() is that it changes the problem without removing any confusion.  Maybe I'm not quite getting it so here's the example I thought of:

File B does a thing.
File C requires B.
File C requires and uses B before it's amended.  Consider C may be a module.
File A amends B.
File D requires A.
File D loads and runs with an amended B.
File C is now executing with a different B than it was loaded with.

If you just stuck file A in the compiler options as the first namespace, then you would be assured B was always amended before anything tried to use it.  Nobody can accidentally break this by changing the tree with provide and require statements as you can when using amends.

I like the idea of getting the concerns for this along side provide() and require(), but I don't think amend() makes it easier.

Michael Bolin

unread,
Oct 26, 2011, 2:36:32 PM10/26/11
to Closure Library Discuss
Hi David, good catch! That is an edge case that I had not thought of.
I think this could be solved by requiring files with goog.amend() to
be in the same module as the namespace that they are amending.

Also, I would argue that in the overwhelming majority of cases, a file
that goog.amend()s something should not goog.provide() anything. The
only time it should do so is when a namespace is goog.amend()ed and
those amends need to be ordered.

Or we could just prohibit multiple amends (or not guarantee order of
multiple amends) and prohibit goog.provide()s in them altogether.

Nick Santos

unread,
Oct 26, 2011, 2:45:18 PM10/26/11
to closure-lib...@googlegroups.com
On Wed, Oct 26, 2011 at 2:36 PM, Michael Bolin <boli...@gmail.com> wrote:
> Hi David, good catch! That is an edge case that I had not thought of.
> I think this could be solved by requiring files with goog.amend() to
> be in the same module as the namespace that they are amending.
>
> Also, I would argue that in the overwhelming majority of cases, a file
> that goog.amend()s something should not goog.provide() anything. The
> only time it should do so is when a namespace is goog.amend()ed and
> those amends need to be ordered.

We are trying to move towards a world where everything provides some
symbol. That way, you can simply pass all the files in a directory to
a tool, and tell the tool what the entry points are, and it will
remove files that you aren't using.

I think this proposal would work well for a dependency tree of D = 2
(where you develop application X, and X depends on Closure). But I
think it would get unmanageable for any D > 2 (where X depends on some
other library Y, and X and Y both depend on Closure).

Michael Bolin

unread,
Oct 26, 2011, 3:13:13 PM10/26/11
to Closure Library Discuss
Hi Nick, I think that is an accurate assessment of the tradeoffs
(works well for D = 2, but possibly problematic for D > 2). Arguably,
you could just have a subdirectory of your library code named "mods",
so if you depend on Y and you point at Y's directory, you
automatically pull in Y's mods, as well. (In that scenario, "mods"
would not have to be a special name for the Compiler, but rather a
conventional place in which to put goog.amend() files.)

Though do you have any alternative suggestions for my two scenarios
above? You could argue that you should create a custom subclass for
goog.ui.Component, but other libraries also return
goog.async.Deferreds, so if you want to be able to chain arbitrary
deferreds together and whatnot, then you really want to make sure that
all of them have the same methods.

I'm also interested in scenarios where use cases #1 and #2 have
different solutions.


On Oct 26, 2:45 pm, Nick Santos <nicksan...@google.com> wrote:

David Turnbull

unread,
Oct 26, 2011, 3:27:06 PM10/26/11
to closure-lib...@googlegroups.com
So if you amend('something') it's basically saying that when the provide('something') file is first needed you automatically insert the amend() file immediately after.  I think that would work and without any special exception for modules.  

Getting into an amend() hierarchy is where it gets weird.  I would like to throw an error on a duplicate amend() just like a duplicated provide().  If provide() ever allows for multiples then the pattern can be extended to amend().

David Turnbull

unread,
Oct 26, 2011, 3:54:22 PM10/26/11
to closure-lib...@googlegroups.com
Hey, wait, I just said "If provide() ever allows for multiples then the pattern can be extended to amend()."

But if provide() can define multiples and hierarchy then I think that entirely satisfies all your original requirements for amend().

Nick Santos

unread,
Oct 26, 2011, 3:54:45 PM10/26/11
to closure-lib...@googlegroups.com
I think the general problem is that we (and by "we", i mean "people
who write JavaScript") tend to conflate dependency-order with
initialization-order.

I can think of lots of one-offs to shim that problem (the compiler's
--define flag is one example of this, goog.amend is another), but I'm
worried about the proliferation of lots of shims that make the library
confusing and difficult to reason about. If we ever have
goog.amend.amend, we've gone too far :)

Folks have suggested elsewhere that Closure should move towards modules
http://wiki.ecmascript.org/doku.php?id=harmony:modules
or some requirejs-like solution, where the module is not necessarily
initialized when it is declared. That would make it a lot easier to
add hooks like this, so you could tell the module loader "make sure
the base.js module hasn't loaded yet, and call this handler if it does
load".

Alternatively, i've heard proposals that there should be a compiler
mode where we outright forbid certain types of initialization in the
global scope, and instead force you to register a static
initialization block. I vaguely recall that this is basically how GWT
works (because they have to implement Java's late-initializer logic in
JS).

Nick Santos

unread,
Oct 26, 2011, 4:54:04 PM10/26/11
to closure-lib...@googlegroups.com
also, to answer your other question, i suspect you can solve case #1
by adding something like
goog.CSS_MAP_ = !COMPILED && goog.global.EXTERNAL_CSS_MAP;

You only need to do this in non-compiled mode, right?

Reply all
Reply to author
Forward
0 new messages