Closure: The Definitive Guide [JAVASCRIPT][Closure. Guide]

%5BJAVASCRIPT%5D%5BClosure.%20The%20Definitive%20Guide%5D

%5BJAVASCRIPT%5D%5BClosure.%20The%20Definitive%20Guide%5D

%5BJAVASCRIPT%5D%5BClosure.%20The%20Definitive%20Guide%5D

Closure_The_Definitive_Guide

%5BClosure%20The%20Definitive%20Guide%20by%20Michael%20Bolin%20-%202010%5D

User Manual:

Open the PDF directly: View PDF PDF.
Page Count: 594

DownloadClosure: The Definitive Guide [JAVASCRIPT][Closure. Guide]
Open PDF In BrowserView PDF
www.it-ebooks.info

www.it-ebooks.info

Closure: The Definitive Guide

Michael Bolin

Beijing • Cambridge • Farnham • Köln • Sebastopol • Tokyo

www.it-ebooks.info

Closure: The Definitive Guide
by Michael Bolin
Copyright © 2010 Michael Bolin. All rights reserved.
Printed in the United States of America.
Published by O’Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472.
O’Reilly books may be purchased for educational, business, or sales promotional use. Online editions
are also available for most titles (http://my.safaribooksonline.com). For more information, contact our
corporate/institutional sales department: (800) 998-9938 or corporate@oreilly.com.

Editors: Simon St.Laurent and Julie Steele
Production Editor: Kristen Borg
Copyeditor: Nancy Kotary
Proofreader: Kristen Borg

Indexer: Ellen Troutman Zaig
Cover Designer: Karen Montgomery
Interior Designer: David Futato
Illustrator: Robert Romano

Printing History:
September 2010:

First Edition.

Nutshell Handbook, the Nutshell Handbook logo, and the O’Reilly logo are registered trademarks of
O’Reilly Media, Inc. Closure: The Definitive Guide, the image of a golden plover, and related trade dress
are trademarks of O’Reilly Media, Inc.
Many of the designations used by manufacturers and sellers to distinguish their products are claimed as
trademarks. Where those designations appear in this book, and O’Reilly Media, Inc., was aware of a
trademark claim, the designations have been printed in caps or initial caps.
While every precaution has been taken in the preparation of this book, the publisher and author assume
no responsibility for errors or omissions, or for damages resulting from the use of the information contained herein.

ISBN: 978-1-449-38187-5
[M]
1283888246

www.it-ebooks.info

Table of Contents

Foreword . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiii
Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xvii
My Experiences with Closure
Audience
ECMAScript Versus JavaScript
Using This Book
Acknowledgments

xviii
xx
xx
xxi
xxiv

1. Introduction to Closure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Tools Overview
Closure Library
Closure Templates
Closure Compiler
Closure Testing Framework
Closure Inspector
Closure Design Goals and Principles
Reducing Compiled Code Size Is Paramount
All Source Code Is Compiled Together
Managing Memory Matters
Make It Possible to Catch Errors at Compile Time
Code Must Work Without Compilation
Code Must Be Browser-Agnostic
Built-in Types Should Not Be Modified
Code Must Work Across Frames
Tools Should Be Independent
Downloading and Installing the Tools
Closure Library and Closure Testing Framework
Closure Templates
Closure Compiler
Closure Inspector

2
2
3
3
4
4
5
5
6
6
7
7
7
8
8
8
9
10
11
12
12

iii

www.it-ebooks.info

Example: Hello World
Closure Library
Closure Templates
Closure Compiler
Closure Testing Framework
Closure Inspector

12
13
14
17
19
21

2. Annotations for Closure JavaScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
JSDoc Tags
Type Expressions
Simple Types and Union Types
Function Types
Record Types
Special @param Types
Subtypes and Type Conversion
The ALL Type
JSDoc Tags That Do Not Deal with Types
Constants
Deprecated Members
License and Copyright Information
Is All of This Really Necessary?

25
29
29
31
32
33
38
41
41
42
43
43
43

3. Closure Library Primitives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Dependency Management
calcdeps.py
goog.global
COMPILED
goog.provide(namespace)
goog.require(namespace)
goog.addDependency(relativePath, provides, requires)
Function Currying
goog.partial(functionToCall, ...)
goog.bind(functionToCall, selfObject, ...)
Exports
goog.getObjectByName(name, opt_object)
goog.exportProperty(object, propertyName, value)
goog.exportSymbol(publicPath, object, opt_objectToExportTo)
Type Assertions
goog.typeOf(value)
goog.isDef(value)
goog.isNull(value)
goog.isDefAndNotNull(value)
goog.isArray(obj)
iv | Table of Contents

www.it-ebooks.info

45
45
47
48
48
50
51
54
54
57
58
58
58
60
61
62
62
63
63
63

goog.isArrayLike(obj)
goog.isDateLike(obj)
goog.isString(obj), goog.isBoolean(obj), goog.isNumber(obj)
goog.isFunction(obj)
goog.isObject(obj)
Unique Identifiers
goog.getUid(obj)
goog.removeUid(obj)
Internationalization (i18n)
goog.LOCALE
goog.getMsg(str, opt_values)
Object Orientation
goog.inherits(childConstructorFunction, parentConstructorFunction)
goog.base(self, opt_methodName, var_args)
goog.nullFunction
goog.abstractMethod
goog.addSingletonGetter(constructorFunction)
Additional Utilities
goog.DEBUG
goog.now()
goog.globalEval(script)
goog.getCssName(className, opt_modifier),
goog.setCssNameMapping(mapping)

64
64
64
65
65
65
65
66
67
67
68
68
68
69
69
70
70
70
70
71
71
71

4. Common Utilities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
goog.string
goog.string.htmlEscape(str, opt_isLikelyToContainHtmlChars)
goog.string.regExpEscape(str)
goog.string.whitespaceEscape(str, opt_xml)
goog.string.compareVersions(version1, version2)
goog.string.hashCode(str)
goog.array
goog.array.forEach(arr, func, opt_obj)
Using Iterative goog.array Functions in a Method
goog.object
goog.object.get(obj, key, opt_value)
goog.setIfUndefined(obj, key, value)
goog.object.transpose(obj)
goog.json
goog.json.parse(str)
goog.json.unsafeParse(str)
goog.json.serialize(obj)
goog.dom

75
75
77
78
78
79
79
80
81
82
82
83
83
84
85
85
86
86

Table of Contents | v

www.it-ebooks.info

goog.dom.getElement(idOrElement)
goog.dom.getElementsByTagNameAndClass(nodeName, className,
elementToLookIn)
goog.dom.getAncestorByTagNameAndClass(element, tag,
className)
goog.dom.createDom(nodeName, attributes, var_args)
goog.dom.htmlToDocumentFragment(htmlString)
goog.dom.ASSUME_QUIRKS_MODE and
goog.dom.ASSUME_STANDARDS_MODE
goog.dom.classes
goog.dom.classes.get(element)
goog.dom.classes.has(element, className)
goog.dom.classes.add(element, var_args) and
goog.dom.classes.remove(element, var_args)
goog.dom.classes.toggle(element, className)
goog.dom.classes.swap(element, fromClass, toClass)
goog.dom.classes.enable(element, className, enabled)
goog.userAgent
Rendering Engine Constants
Platform Constants
goog.userAgent.isVersion(version)
goog.userAgent.product
goog.net.cookies
goog.net.cookies.isEnabled()
goog.net.cookies.set(name, value, opt_maxAge, opt_path,
opt_domain)
goog.net.cookies.get(name, opt_default)
goog.net.cookies.remove(name, opt_path, opt_domain)
goog.style
goog.style.getPageOffset(element)
goog.style.getSize(element)
goog.style.getBounds(element)
goog.style.setOpacity(element, opacity)
goog.style.setPreWrap(element)
goog.style.setInlineBlock(element)
goog.style.setUnselectable(element, unselectable, opt_noRecurse)
goog.style.installStyles(stylesString, opt_node)
goog.style.scrollIntoContainerView(element, container, opt_center)
goog.functions
goog.functions.TRUE
goog.functions.constant(value)
goog.functions.error(message)

vi | Table of Contents

www.it-ebooks.info

86
87
89
91
92
93
95
95
95
96
96
97
98
98
99
101
102
102
104
104
104
105
105
105
105
106
106
106
106
106
107
107
108
108
108
108
109

5. Classes and Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
Example of a Class in Closure
Closure JavaScript Example
Equivalent Example in Java
Static Members
Singleton Pattern
Example of a Subclass in Closure
Closure JavaScript Example
Equivalent Example in Java
Declaring Fields in Subclasses
@override and @inheritDoc
Using goog.base() to Simplify Calls to the Superclass
Abstract Methods
Example of an Interface in Closure
Multiple Inheritance
Enums
goog.Disposable
Overriding disposeInternal()

112
112
115
116
118
119
119
123
124
125
126
127
128
130
132
132
133

6. Event Management . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
A Brief History of Browser Event Models
Closure Provides a Consistent DOM Level 2 Events API Across Browsers
goog.events.listen()
goog.events.EventTarget
goog.events.Event
goog.events.EventHandler
Handling Keyboard Events

137
138
138
141
146
148
152

7. Client-Server Communication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
Server Requests
goog.net.XmlHttp
goog.net.XhrIo
goog.net.XhrManager
goog.Uri and goog.uri.utils
Resource Loading and Monitoring
goog.net.BulkLoader
goog.net.ImageLoader
goog.net.IframeLoadMonitor
goog.net.MultiIframeLoadMonitor
goog.net.NetworkTester
Cross-Domain Communication
goog.net.jsonp
goog.net.xpc

155
155
156
161
163
165
165
167
168
169
169
170
171
173
Table of Contents | vii

www.it-ebooks.info

Uploading Files
Comet

176
178

8. User Interface Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
Design Behind the goog.ui Package
goog.ui.Component
Basic Life Cycle
Components with Children
Events
States
Errors
goog.ui.Control
Handling User Input
Managing State
Delegating to the Renderer
Example: Responding to a Mouseover Event
goog.ui.Container
Using Common Components
Pulling in CSS
goog-inline-block
Example of Rendering a Component: goog.ui.ComboBox
Example of Decorating a Control: goog.ui.Button and
goog.ui.CustomButton
Creating Custom Components
example.Checklist and example.ChecklistItem
example.ui.ChecklistItem and example.ui.ChecklistItemRenderer
example.ui.Label
example.ui.Checklist and example.ui.ChecklistRenderer
Rendering Example
Decorating Example
Conclusions

182
184
184
190
194
195
196
197
198
199
201
206
206
210
212
215
218
220
227
228
229
232
233
236
237
239

9. Rich Text Editor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
Design Behind the goog.editor Package
Trade-offs: Control, Code Size, and Performance
goog.editor.BrowserFeature
Creating an Editable Region
goog.editor.Field
goog.editor.SeamlessField
Extending the Editor: The Plugin System
Registering Plugins
Interacting with Plugins
goog.editor.Plugin
viii | Table of Contents

www.it-ebooks.info

241
242
243
243
244
251
253
253
254
256

Built-in Plugins
Custom Plugins
UI Components
Dialogs
Toolbar
Selections
goog.dom.Range
goog.dom.AbstractRange
goog.editor.range

260
265
270
270
274
278
279
281
285

10. Debugging and Logging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289
Creating Logging Information
goog.debug.LogRecord
goog.debug.Logger.Level
goog.debug.Logger
Displaying Logging Information
goog.debug.Console
goog.debug.DivConsole
goog.debug.DebugWindow
goog.debug.FancyWindow
Profiling JavaScript Code
Reporting JavaScript Errors

290
290
291
292
297
298
298
298
299
300
302

11. Closure Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303
Limitations of Existing Template Systems
Server-Side Templates
Client-Side Templates
Introducing Closure Templates
Creating a Template
Declaring Templates with {namespace} and {template}
Commenting Templates
Overriding Line Joining with {sp} and {nil}
Writing Raw Text with {literal}
Building Soy Expressions
Displaying Data with {print}
Managing Control Flow with {if}, {elseif}, and {else}
Advanced Conditional Handling with {switch}, {case}, and {default}
Looping over Lists with {foreach}
Leveraging Other Templates with {call} and {param}
Identifying CSS Classes with {css}
Internationalization (i18n)
Compiling Templates
Compiling a Template for JavaScript

303
303
304
305
306
309
310
310
312
312
315
316
317
318
319
321
321
322
323

Table of Contents | ix

www.it-ebooks.info

Compiling a Template for Java
Defining a Custom Function

326
328

12. Using the Compiler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 333
Benefits of Using the Compiler
Reducing Code Size
Catching Errors at Compile Time
Protecting Code Through Obfuscation
How the Compiler Works
Compiler Options
Compilation Levels
Formatting Options
Warning Levels
Running the Compiler
Closure Compiler Service UI
Closure Compiler Service API
Closure Compiler Application
Programmatic Java API
Integrating the Compiler into a Build Process
Partitioning Compiled Code into Modules
Introducing the Application Code
Introducing the Module Loading Code
Partitioning the Input
Loading the Modules
Refining the Partitioning

334
334
335
336
337
338
338
343
344
346
346
349
351
354
357
363
365
368
370
373
376

13. Advanced Compilation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379
What Happens During Compilation
Externs and Exports
Property Flattening
Property Renaming
Preparing Code for the Compiler
Input Language
Programmatic Evaluation of Strings of JavaScript Code
Never Use the with Keyword
Checks Provided by the Compiler
Type Checking
Access Controls
Optimizations Performed by the Compiler
Processing Closure Primitives
Devirtualizing Prototype Methods
Inlining

x | Table of Contents

www.it-ebooks.info

380
383
400
404
406
406
407
408
408
408
414
417
417
418
421

14. Inside the Compiler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 427
Tour of the Codebase
Getting and Building the Compiler
Compiler.java
CompilerPass.java
JSSourceFile.java
CompilerOptions.java
CompilationLevel.java
WarningLevel.java
PassFactory.java
DefaultPassConfig.java
CommandLineRunner.java
com.google.common.collect
Hidden Options
Checks
Renaming
Optimizations
Output
Example: Extending CommandLineRunner
Example: Visualizing the AST Using DOT
What Is DOT?
Converting the AST to DOT
Hooking into MyCommandLineRunner
Example: Creating a Compiler Check
Example: Creating a Compiler Optimization

427
427
431
432
433
433
433
434
434
434
435
435
436
436
440
442
448
450
452
453
453
455
456
460

15. Testing Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 465
Creating Your First Test
Example: Testing an Email Validator
Assertions
Life Cycle of a Test Case
Differences from JsUnit
Mock Objects
goog.testing.PropertyReplacer
goog.testing.PseudoRandom
goog.testing.MockClock
Testing to Ensure That an Error Is Thrown
Testing Input Events
Testing Asynchronous Behavior
goog.testing.ContinuationTestCase
goog.testing.AsyncTestCase
Running a Single Test
Running Multiple Tests

466
466
471
474
475
476
476
478
479
482
483
483
483
487
489
490
Table of Contents | xi

www.it-ebooks.info

Automating Tests
System Testing

492
494

16. Debugging Compiled JavaScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 497
Verify That the Error Occurs in Uncompiled Mode
Format Compiled Code for Debugging
Compile with --debug=true
Use the Closure Inspector

497
498
500
501

A. Inheritance Patterns in JavaScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 505
Example of the Functional Pattern
Example of the Pseudoclassical Pattern
Drawbacks to the Functional Pattern
Potential Objections to the Pseudoclassical Pattern
Won’t Horrible Things Happen if I Forget the New Operator?
Didn’t Crockford Also Say I Wouldn’t Have Access to Super Methods?
Won’t All of the Object’s Properties Be Public?
Won’t Declaring SomeClass.prototype for Each Method and Field of
SomeClass Waste Bytes?
I Don’t Need Static Checks—My Tests Will Catch All of My Errors!

505
506
508
511
511
512
512
512
513

B. Frequently Misunderstood JavaScript Concepts . . . . . . . . . . . . . . . . . . . . . . . . . . . . 515
JavaScript Objects Are Associative Arrays Whose Keys Are Always Strings
There Are Several Ways to Look Up a Value in an Object
Single-Quoted Strings and Double-Quoted Strings Are Equivalent
There Are Several Ways to Define an Object Literal
The prototype Property Is Not the Prototype You Are Looking For
The Syntax for Defining a Function Is Significant
What this Refers to When a Function Is Called
The var Keyword Is Significant
Block Scope Is Meaningless

515
516
516
517
520
523
524
526
527

C. plovr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 531
Getting Started with plovr
Config Files
Build Command
Serve Command
Displaying Compiler Errors
Auditing Compiled Code Size
Generating Externs from Exports
Generating a Source Map

532
532
534
535
537
538
539
540

Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 541
xii | Table of Contents

www.it-ebooks.info

Foreword

I was sitting on a balcony on the west side of Manhattan, sipping on a warm glass of
scotch with a few others. Michael Bolin joined us. Michael wrote this book. At the time,
Michael was working on Google Tasks. I was the tech lead on our JavaScript optimizer,
later named Closure Compiler. Michael didn’t join us to talk about JavaScript optimization though. He didn’t want to talk scotch either, to his detriment. He wanted to talk
JavaScript-driven text editing, and thus he wanted to talk to Julie.
You will receive a proper introduction to Julie in Chapter 9, but for now, just know
that Julie is our expert on how text editors are implemented in each web browser.
Michael found that, when managing a task list in a web browser, you want a few features
built into your plain text editor. You want to make words bold for emphasis. You want
a keyboard shortcut to move your cursor to the next task item. He didn’t want to have
to write a whole editor. He just wanted a few tweaks on top of what the browser provides, to make the experience smoother for the user. How would you implement this?
Julie explained that there are many, many choices for such a thing. “Should you use a
textarea?” “Should you use a contentEditable region?” “Should you rely on the
browser’s built-in rich text functions?” “Should you implement the ‘bold’ function in
JavaScript?” “How do you make sure the cursor ends up on the right line, given that
browsers each implement cursor selection differently?” “Should you put all the text
editing in an iframe to isolate it from the rest of the page?Ӡ
“Is there code you can reuse for this?”
You don’t really want to implement all these things from scratch. A lot of them will
need to call into esoteric browser APIs in complex ways. Many of those APIs are buggy,
poorly documented, or simply do not perform very well. For some of those APIs, it’s
easier to read the browser source code than to find reasonable documentation.

† Fun fact: as the number of JavaScript developers in a room increases, the probability that someone will suggest
“iframes” as the solution to your problem asymptotically approaches 1.

xiii

www.it-ebooks.info

You’ll find answers to many of those specific questions throughout this book. But I
think the question that the book is most interested in (and rightly so) is about how to
make it easy to reuse code for Ajax apps. It spins off into a few other equally substantial
questions.
How do you share JavaScript code? How do you organize large amounts of common
JavaScript, often built for highly specialized tasks? How do you weigh one team’s need
for boatloads of new features and customizations against another team’s need to keep
the size of the JavaScript they’re sending to the user small?
The Closure Tools were designed to solve many of these problems. Maybe that’s understating the point. These problems are at the very core of their design. Many of the
tools were started by our friends on Gmail. Gmail began as a relatively modest JavaScript app. Then they added more and more features, and watched it grow beyond any
hope of control or maintainability. Frederick P. Brooks, Jr., famously described largesystem programming as “a tar pit, and many great and powerful beasts have thrashed
violently in it.” In a language like JavaScript, a highly dynamic environment where
almost everything can be mutated and there’s no standard way to specify contracts
(type checking or otherwise), the tar is fast and can suck down even a small group of
developers.
The Closure Tools developers tried to bring “closure” to this mess. (I agree the pun is
terrible. It is not mine.) They followed strict idioms for namespacing code and defining
classes. They adopted ECMAScript 4’s type language for specifying contracts. The
compiler forced the developer to declare their variables, and emitted warnings for other
frowned-upon idioms. The Closure Tools, in short, tried to add some structure to the
language. Many engineering teams at Google found this structure useful, and built their
products on top of it.
A long time passed. The Closure Tools remained proprietary for years. This wasn’t
meant to be. Both the compiler and the libraries were always designed to be open source
projects. But more importantly, they were designed for building Google apps first, and
to be open source projects second. So releasing them publicly took a back seat to other
things.
Have you ever tried to publicly open up the code of a proprietary project? Several engineers had tried to release Closure Compiler. They had all given up. It is surprisingly
difficult. There are two major parts. First, you have to release the code: port it to a
public build system like Apache Ant, remove all of its nonopen dependencies, and
rewrite any dependencies that you can’t remove. Second, you have to write documentation: loads of documentation.
You can imagine how skeptical I was when Michael first came by my desk to talk about
making Closure Compiler an open source project. This was early 2009. By this point,
“publicly releasing Closure Compiler” was the sort of daunting chore that you’ve procrastinated forever and a half. We’d work on it for a month, realize that we seemed no

xiv | Foreword

www.it-ebooks.info

closer to completion, and then procrastinate some more. It was sort of like reading
Infinite Jest. Or cleaning my apartment.
Obviously, Michael succeeded in his effort to release the compiler. I think it was some
combination of being persistent, asking a lot of good questions, and commissioning a
lot of good help from smart people. Of course, Michael is a web app developer first,
and a open source engineer second, so he also helped design and write the Closure
Compiler web frontend. By pure serendipity, Closure Library, Closure Templates, and
Closure Debugger were all released along with it.
But making the code available was just the first part of opening up the project. This
book marks a major milestone in the second: documenting it all. There’s surprisingly
comprehensive knowledge in this book, more than any one engineer on the project
knows. I’ve already started telling our interns to stop bothering me, and instead just
read this Closure book’s sections on appending DocumentFragments, or on using
XHRs, or on the binding of the “this” keyword. You can read this book like an API
reference manual for the Closure Tools. You can even read it more generally as an API
reference for web application development.
If you want to get the most out of it, pay attention to Michael’s explanations of how
and why these tools came to be. Michael explains how they can help you to manage
complexity. There were many missteps and false starts. Along the way, Michael will
drop hints about pitfalls to watch out for, mistakes that we made and how you can
avoid them too. You’ll even learn how to build your own tools and compiler plugins
to help tame your own large codebase.
Just remember that this is first and foremost a practical guide to how to build your own
rich web apps. So quit reading this foreword and go to it!
—Nick Santos
Former Closure Compiler Tech Lead

Foreword | xv

www.it-ebooks.info

www.it-ebooks.info

Preface

JavaScript borrows many great ideas from other programming languages, but its most
unique, and perhaps most powerful, feature is that any code written in JavaScript can
run as-is in any modern web browser. This is a big deal, and it is unlikely to change
anytime soon.
As web browsers improve and become available on more devices, more applications
are being ported from desktop applications to web applications. With the introduction
of HTML5, many of these applications will be able to work offline for the first time. In
order to create a superior user experience, much of the logic that was previously done
on the server will also have to be available on the client. Developers who have written
their server logic in Java, Python, or Ruby will have to figure out how to port that server
logic to JavaScript. Tools like Google Web Toolkit, which translate Java to JavaScript
can help with this, though such tools are often clumsy because the idioms from one
programming language do not always translate smoothly into that of another. However, if your server code is written in JavaScript, this is not an issue.
I believe that the use of server-side JavaScript (SSJS) is just beginning. Previously, most
implementations of JavaScript were too slow to be considered a viable option for server
code. Fortunately, the recent competition among browser vendors to have the fastest
JavaScript engine makes that difference far less significant (http://shootout.alioth.debian
.org).
Because of the emerging support for offline web applications, it is compelling to write
both the client and the server in the same programming language to avoid the perils
associated with maintaining parallel implementations of the same logic. Because it is
extremely unlikely that all of the major browser vendors will adopt widespread support
for a new programming language, that will continue to force the client side of a web
application to be written in JavaScript, which in turn will pressure developers to write
their servers in JavaScript as well. This means that the size of the average JavaScript
codebase is likely to increase dramatically in the coming years, so JavaScript developers
will need better tools in order to manage this increased complexity. I see Closure as the
solution to this problem.

xvii

www.it-ebooks.info

Closure is a set of tools for building rich web applications with JavaScript, and brings
with it a new approach to writing JavaScript and maintaining large JavaScript applications. Each tool in the suite is designed to be used independently (so jQuery developers
can make use of the Closure Compiler and Closure Templates, even if they are not
interested in the Closure Library), but they are most effective when used together.
Many JavaScript toolkits today focus on DOM utilities and UI widgets. Such functionality is incredibly useful when building the interface for a web application, but the
emergence of SSJS will require an equivalent effort in building server-side JavaScript
libraries. There, the focus is likely to be on data structures and efficient memory usage,
both of which are already woven into the Closure framework.
I believe that Closure will play an important part in making web applications faster and
more reliable. As an active user of the Web, I have a vested interest in making sure this
happens. That’s why I had to write this book. Rather than document every API in
Closure, I have tried to provide detailed explanations for the most commonly used
APIs, particularly those that are unique to the Closure approach.
Indeed, learning Closure will change the way you develop JavaScript applications.

My Experiences with Closure
When I worked at Google from 2005 to 2009, I used Closure to help build Google
Calendar and Google Tasks. When the initial work on Calendar was done in 2005, only
the Compiler was available, and it was (and is) known internally as the JavaScript
Compiler. At the time, there were a number of common JavaScript utilities that teams
would copy from one another. This led to many forked versions, so improvements to
one copy did not propagate to the others.
Meanwhile, the JavaScript codebase for Gmail had grown so large and complex that
developers complained that it was too hard for them to add new features. This triggered
a rewrite of the Gmail client, which precipitated the development of the two other major
tools in the Closure suite: the Library and Templates. The Library was simply named
“Closure,” as it was a play on the programming construct used so frequently in JavaScript, as well as the idea that it would bring “closure” to the nightmare that was
JavaScript development at Google.
Like many other JavaScript toolkits, the goal of Closure was to provide a comprehensive
cross-browser library. Instead of adopting an existing solution, such as Dojo, Google
decided to roll its own. By having complete control of its library, it could ensure that
the API would be stable and that the code would work with its (then) secret weapon:
the Closure Compiler. This made it possible to buck the trend established by libraries
like Prototype that encouraged the use of absurdly short function names. In Closure,
nondescript function names such as $ were eschewed in favor of more descriptive ones
because the Compiler would be responsible for replacing longer names with shorter
ones.
xviii | Preface

www.it-ebooks.info

The build system at Google was amended to express dependencies between JavaScript
files (these relationships are reflected by goog.provide() and goog.require() statements
in the Closure Library). For the first time, dependencies were organized into wellnamed packages, which introduced a consistent naming scheme and made utilities
easier to find. In turn, this made code reuse more straightforward, and the Library
quickly achieved greater consistency and stability than the previous dumping ground
of JavaScript utilities. This new collection of common code was far more trustworthy,
so teams started to link to it directly rather than fork their own versions, as they were
no longer afraid that it would change dramatically out from under them.
Finally, Closure Templates (known internally as Soy) were created to address the problem that most existing templating systems were designed to generate server code, but
not JavaScript code. The first version of Soy generated only JavaScript, but it was later
extended to generate Java as well, to provide better support for the “HTML Decorator”
pattern described in Chapter 8, User Interface Components.
By the time I started work on Google Tasks, these tools had matured considerably.
They were invaluable in creating Tasks. While the Calendar team was busy replacing
their original utility functions with Closure Library code and swapping out their homebrewed (or Bolin-brewed) template solution with Soy, I was able to make tons of progress on Tasks because I was starting with a clean slate. Because Gmail has been stung
by hard-to-track-down performance regressions in the past, the barrier for getting code
checked in to Gmail is high. In integrating Tasks with Gmail, I was forced to gain a
deeper understanding of the Closure Tools so I could use them to optimize Tasks to
the satisfaction of the Gmail engineers. Later, when I integrated Tasks in Calendar, I
learned how to organize a sizable JavaScript codebase so it could be incorporated by
even larger JavaScript projects.
One of my major takeaways from using Closure is that trying to address limitations of
the JavaScript programming language with a JavaScript library is often a mistake. For
example, JavaScript does not have support for multiline strings (like triple-quote in
Python), which makes it difficult to create templates for HTML. A bad solution (which
is the one I created for Google Calendar back in 2005 that they were still trying to phase
out so they could replace it with Soy in 2009) is to create a JavaScript library like jQuery
Templates (http://plugins.jquery.com/project/jquerytemplate). Such a library takes a
string of JavaScript as the template and parses it at runtime with a regular expression
to extract the template variables. The appeal, of course, is that implementing something
like jQuery Templates is fairly easy, whereas implementing a template solution that is
backed by an actual parser is fairly hard (Closure Templates does the latter). In my
experience, it is much better to create a tool to do exactly what you want (like Closure
Templates) than it is to create a construct within JavaScript that does almost what you
want (like jQuery Templates). The former will almost certainly take longer, but it will
pay for itself in the long run.

Preface | xix

www.it-ebooks.info

Audience
As this is a book about Closure, a suite of JavaScript tools, it assumes that you are
already familiar with JavaScript. Nevertheless, because so many JavaScript programmers learn the language by copying and pasting code from existing websites, Appendix B is included to try to identify incorrect assumptions you may have made about
JavaScript when coming from your favorite programming language. Even those who
are quite comfortable with the language are likely to learn something.
Other than the Closure Tools themselves, this book does not assume that you are
already familiar with other JavaScript tools (such as JSLint and YUI Compressor) or
libraries (such as Dojo and jQuery), though sometimes parallels will be drawn for the
benefit of those who are trying to transfer their knowledge of those technologies in
learning Closure. The one exception is Firebug, which is a Firefox extension that helps
with web development. In addition to being considered an indispensable tool for the
majority of web developers, it must be installed in order to use the Closure Inspector.
Unlike the other tools in the suite, the use of the Closure Inspector is tied to a single
browser: Firefox. Because Firebug is updated frequently and has comprehensive documentation on its website, this book does not contain a tutorial on Firebug because it
would likely be outdated and incomplete. http://getfirebug.com should have everything
you need to get started with Firebug.
Finally, this book makes a number of references to Java when discussing Closure.
Although it is not necessary to know Java in order to learn Closure, it is helpful to be
familiar with it, as there are elements of Java that motivate the design of the Closure
Library. Furthermore, both Closure Templates and the Closure Compiler are written
in Java, so developers who want to modify those tools will need to know Java in order
to do so. This book will not teach you Java, though a quick search on Amazon will
reveal that there of hundreds of others that are willing to do so.

ECMAScript Versus JavaScript
This book includes several references to ECMAScript, as opposed to JavaScript, so it
is important to be clear on the differences between the two. ECMAScript is a scripting
language standardized by Ecma International, and JavaScript is an implementation of
that standard. Originally, JavaScript was developed by Netscape, so Microsoft developed its own implementation of ECMAScript named JScript. This means that technically, “ECMAScript” should be used to refer to the scripting language that is universally
available on all modern web browsers, though in practice, the term “JavaScript” is used
instead. To quote Brendan Eich, the creator of JavaScript: “ECMAScript was always
an unwanted trade name that sounds like a skin disease.” To be consistent with colloquial usage (and honestly, just because it sounds better), JavaScript is often used to
refer to ECMAScript in this book.

xx | Preface

www.it-ebooks.info

However, ECMAScript is mentioned explicitly when referring to the standard. The
third edition of the ECMAScript specification (which is also referred to as ES3) was
published in December 1999. As it has been around for a long time, it is implemented
by all modern web browsers. More recently, the fifth edition of the ECMAScript specification (which is also referred to as ES5) was published in December 2009. (During
that 10-year period, there was an attempt at an ES4, but it was a political failure, so it
was abandoned.) As ES5 is a relatively new standard, no browser implements it fully
at the time of this writing. Because Closure Tools are designed to create web applications that will run on any modern browser, they are currently designed around ES3.
However, the Closure developers are well aware of the upcoming changes in ES5, so
many of the newer features of Closure are designed with ES5 in mind, with the expectation that most users will eventually be using browsers that implement ES5.

Using This Book
This book explains all of the Closure Tools in the order they are most likely to be used.
• Chapter 1, Introduction to Closure, introduces the tools and provides a general
overview of how they fit together with a complete code example that exercises all
of the tools.
When working on a JavaScript project, you will spend the bulk of your time designing
and implementing your application. Because of this, the majority of the book is focused
on how to leverage the Closure Library and Closure Templates to implement the functionality you desire. Of all the topics covered in this part of the book, the rich text editor
is the one that appears most frequently in the Closure Library discussion group. To
that end, I recruited goog.editor expert Julie Parent as a contributing author, so fortunately for you and for me, Julie wrote Chapter 9.
• Chapter 2, Annotations for Closure JavaScript, explains how to annotate JavaScript
code for use with the Closure Compiler.
• Chapter 3, Closure Library Primitives, provides documentation and commentary
on every public member of base.js in the Closure Library.
• Chapter 4, Common Utilities, surveys functionality for performing common operations with the Closure Library, such as DOM manipulation and user agent
detection.
• Chapter 5, Classes and Inheritance, demonstrates how classes and inheritance are
emulated in Closure.
• Chapter 6, Event Management, explains the design of the Closure Library event
system and the best practices when using it.
• Chapter 7, Client-Server Communication, covers the various ways the goog.net
package in the Closure Library can be used to communicate with the server.

Preface | xxi

www.it-ebooks.info

• Chapter 8, User Interface Components, discusses a number of the UI widgets provided by the Closure Library and documents the life cycle of a Closure widget.
• Chapter 9, Rich Text Editor, examines the rich text editor widget in the Closure
Library in detail. This chapter is written by Julie Parent, who wrote the overwhelming majority of the code for this component.
• Chapter 10, Debugging and Logging, demonstrates how to add logging statements
that can be used during development, but can also be removed in production code.
• Chapter 11, Closure Templates, covers how Templates can be used to generate
parameterized JavaScript and Java functions that generate HTML efficiently.
The next three chapters will explain how to get the most out of your source code using
the Closure Compiler:
• Chapter 12, Using the Compiler, demonstrates how to minify code using the
Compiler.
• Chapter 13, Advanced Compilation, goes beyond the Compiler as a minifier and
explains how to use it as a proper compiler, showing how to identify errors at
compile time and achieve size reductions that go far beyond what ordinary minification can do.
• Chapter 14, Inside the Compiler, explores the source code of the Closure Compiler
itself and reveals how to use it as the basis of your own JavaScript tools.
The remaining chapters will focus on evaluating your code to ensure that it does what
you designed it to do:
• Chapter 15, Testing Framework, explains how to write and run unit tests using the
Framework.
• Chapter 16, Debugging Compiled JavaScript, demonstrates how to find errors in
compiled code using the Closure Inspector.
The first two appendixes provide additional information about JavaScript: they are
designed to enrich your knowledge of the language. The third appendix discusses a
build tool that unites the Closure Tools in a way that makes them easier to use.
• Appendix A, Inheritance Patterns in JavaScript, discusses two approaches for simulating inheritance in JavaScript and focuses on the advantages of the approach
used by Closure.
• Appendix B, Frequently Misunderstood JavaScript Concepts, explains features of
the language that often trip up developers, both old and new.
• Appendix C, plovr, introduces a build tool of the same name that can dramatically
simplify and speed up development with the Closure Tools.

xxii | Preface

www.it-ebooks.info

Conventions Used in This Book
The following typographical conventions are used in this book:
Italic
Indicates new terms, URLs, and email addresses.
Constant width

Used for program listings, as well as within paragraphs to refer to program elements
such as filenames, file extensions, variable or function names, databases, data
types, environment variables, statements, and keywords.
Constant width bold

Shows commands or other text that should be typed literally by the user.
Constant width italic

Shows text that should be replaced with user-supplied values or by values determined by context.
This icon signifies a tip, suggestion, or general note.

This icon indicates a warning or caution.

Using Code Examples
This book is here to help you get your job done. In general, you may use the code in
this book in your programs and documentation. You do not need to contact us for
permission unless you’re reproducing a significant portion of the code. For example,
writing a program that uses several chunks of code from this book does not require
permission. Selling or distributing a CD-ROM of examples from O’Reilly books does
require permission. Answering a question by citing this book and quoting example
code does not require permission. Incorporating a significant amount of example code
from this book into your product’s documentation does require permission.
We appreciate, but do not require, attribution. An attribution usually includes the title,
author, publisher, copyright holder, and ISBN. For example: “Closure: The Definitive
Guide by Michael Bolin (O’Reilly). Copyright 2010 Michael Bolin,
978-1-449-38187-5.”
If you feel your use of code examples falls outside fair use or the permission given here,
feel free to contact us at permissions@oreilly.com.

Preface | xxiii

www.it-ebooks.info

Safari® Books Online
Safari Books Online is an on-demand digital library that lets you easily
search over 7,500 technology and creative reference books and videos to
find the answers you need quickly.
With a subscription, you can read any page and watch any video from our library online.
Read books on your cell phone and mobile devices. Access new titles before they are
available for print, and get exclusive access to manuscripts in development and post
feedback for the authors. Copy and paste code samples, organize your favorites, download chapters, bookmark key sections, create notes, print out pages, and benefit from
tons of other time-saving features.
O’Reilly Media has uploaded this book to the Safari Books Online service. To have full
digital access to this book and others on similar topics from O’Reilly and other publishers, sign up for free at http://my.safaribooksonline.com.

How to Contact Us
Please address comments and questions concerning this book to the publisher:
O’Reilly Media, Inc.
1005 Gravenstein Highway North
Sebastopol, CA 95472
800-998-9938 (in the United States or Canada)
707-829-0515 (international or local)
707 829-0104 (fax)
We have a web page for this book, where we list errata, examples, and any additional
information. You can access this page at:
http://oreilly.com/catalog/9781449381875/
To comment or ask technical questions about this book, send email to:
bookquestions@oreilly.com
For more information about our books, conferences, Resource Centers, and the
O’Reilly Network, see our website at:
http://www.oreilly.com

Acknowledgments
I would like to start out by thanking my contributing author, Julie Parent, for her
outstanding work on the rich text editing chapter, and perhaps more importantly, for
her many years of work on the rich text editor widget itself while working at Google.
What started out as a component for the (now forgotten) Google Page Creator product
xxiv | Preface

www.it-ebooks.info

way back in 2005 has become a critical widget for many Google Apps today (most
notably, Gmail). If they gave out doctorates for the field of “little-known browser bugs
that make rich text editing in the browser nearly impossible,” then Julie would be a
leader in the field and Chapter 9 could have been used as her dissertation. Julie, thank
you so much for putting the same amount of diligence into writing your chapter as you
did in developing the rich text editor in the first place.
Next, I owe a tremendous amount of thanks (and a nice bottle of scotch) to Nick Santos,
who has been a phenomenal technical reviewer. He responded to the call for reviewers
with alacrity and his enthusiasm in the project never waned. In doing a review of this
book, Nick effectively engaged in a 35,000-line code review, and provided so many
corrections and helpful suggestions that this book probably would not even be worth
reading if Nick had not read it first. In addition to all of his work as a reviewer, Nick
played (and continues to play) an active role in open-sourcing the Closure Compiler as
well as its development. You can see the breadth and depth of Nick’s knowledge in the
Closure Compiler discussion group, as he is an extremely active member there, as well.
In addition to Nick, I was fortunate enough to have two other Google engineers who
helped build pieces of the Closure Tools suite to participate in the review process. Erik
Arvidsson (who co-created the Closure Library with Dan Pupius—thanks, Dan!) provided lots of valuable feedback on the chapters on the Library. Likewise, the creator of
Closure Templates, Kai Huang, provided detailed criticisms of the chapter on Soy.
Many thanks to both Erik and Kai for lending their time and expertise to ensure that
the story of their work was told correctly.
As Nick explained in the foreword, taking a closed source project and turning it into
an open source one is a lot of work, so I would also like to recognize those who played
an important role in that process. Nathan Naze, Daniel Nadasi, and Shawn Brenneman
all pitched in to open source the Closure Library. Robby Walker and Ojan Vafai also
helped out by moving the rich text editor code into the Library so that it could be opensourced, as well. Extra thanks to Nathan for continuing to manage the open-sourcing
effort and for giving talks to help get the word out about the Library. It is certainly an
example of well-spent 20% time at Google.
In that same vein, I would also like to thank Dan Bentley for helping ensure that all of
this Closure code made it out into the open. Google is lucky to have him working in
their Open Source Programs Office, as his genuine belief and interest in open source
benefits the entire open source community.
I would also like to thank my former teammates on the Closure Compiler team who
all contributed to the open source effort as well as Compiler development: Robert
Bowdidge, Alan Leung, John Lenz, Nada Amin, and Antonio Vincente. Also, thanks
to our manager, Ram Ramani, who supported this effort the whole way through and
helped coordinate the open source launch. I also want to give credit to our intern, Simon
Mathieu, who worked with me to create the Closure Compiler Service.

Preface | xxv

www.it-ebooks.info

Thank you to Joey Schorr for navigating the world of not just Firefox extensions, but
also Firebug extensions, in order to create and maintain the Closure Inspector. Without
Joey, all of our compiled JavaScript would be filled with alert() statements (though
for some of us, that’s how our uncompiled JavaScript looks, too!).
Five hundred pages later, I now have a much better appreciation for the work of David
Westbrook and Ruth Wang, who as tech writers at Google produced much of the public
documentation for Closure Tools that is freely available on http://code.google.com.
Thanks to both David and Ruth for their attention to detail in explaining what these
Closure shenanigans are all about.
Although I have already dropped the names of a lot of Googlers, I know that there are
many more who have contributed to Closure over the years, so I am sure that I am
leaving some out, and I apologize for any omissions. I hope that all of you continue to
make Closure the best choice when choosing a set of tools for building amazing web
applications. As frontend engineers working on products at Google, your work already
has the opportunity to reach many users around the world. But now that all of Closure
is open source, you have the opportunity to have a similar impact on web developers.
I hope that opportunity does not go to waste!
Believe it or not, there were also people who never worked at Google who also helped
make this book possible. Thank you to my editors, Julie Steele and Simon St.Laurent,
who helped green-light this project back in November 2009, less than a month after
the Closure Tools were even open-sourced. I would also like to thank my “unofficial
editors,” which includes everyone who posted a comment on the Rough Cut, especially
Donald Craig and Derek Slager. Not only did all of you help make this book better, but
you also gave me the confidence that someone was actually going to read this thing
someday and that it was worth writing.
Finally, I would like to thank Sarah, without whose unrelenting patience and support
I would not have been able to finish this book. In many ways, writing a book is a lonely
endeavor, but you never let it get that way because you were there to encourage me
throughout the entire process. I would also like to thank my mom, whose love of books
undoubtedly helped inspire me to write this one. Thanks to my sister Katie for letting
me know when she noticed a jump in my page count graph, as it means a lot to know
that someone out there cares and is paying attention. And last but not least, I would
like to thank my father for betting me $500 that I would not be a published author by
30, which provided the extra motivation I needed to see this book all the way through.
I’ll take my winnings in cash, old man!

xxvi | Preface

www.it-ebooks.info

CHAPTER 1

Introduction to Closure

Closure is a collection of tools for building rich web applications in JavaScript. Each
tool in the suite is open-sourced under the Apache 2.0 license, and is created, maintained, and made available for free by Google. Closure is used in the development of
many web applications at Google, including Gmail, Google Maps, and Google Docs.
The performance and scale of these web applications is a testament to the strength and
sophistication of the Closure Tools suite.
Some developers might balk at the thought of expanding the role of JavaScript in their
web applications. Why should the codebase of a language that relies on global variables
and has no support for namespaces get bigger and more complex? Others may point
out that Google simultaneously offers the Google Web Toolkit (GWT) so that web
developers do not even have to concern themselves with JavaScript. Why do we need
new tools for JavaScript when the tools for avoiding it already exist?
Whether you like it or not, JavaScript is the lingua franca of the Web. Although tools
such as GWT do a reasonable job of abstracting away JavaScript, they also create barriers between you and the metal of the browser. Instead of creating tools to circumvent
JavaScript, why not build tools to address its problems head-on?
This is where Closure comes in: the tools make it significantly easier to maintain a large
JavaScript codebase. Using Closure essentially extends JavaScript to include features
available in other programming languages, such as namespaces, type checking, and
data hiding. Furthermore, it does so without incurring the runtime overhead of previous
approaches (see Appendix B). More importantly, it does not sacrifice the good parts of
JavaScript (prototypal inheritance, regular expression literals, first-class functions) that
are not available in other programming languages, such as Java. This transforms JavaScript from a language one must “deal with” into one that is fun and productive.

1

www.it-ebooks.info

In addition to making your development team happier, using Closure will also make
your users happier. The crown jewel of the suite, the Closure Compiler, can significantly reduce the amount of JavaScript that users will have to download when visiting
your site. It does this by replacing long variable names with shorter ones, removing
unused code, and by applying a variety of other optimizations. In addition to making
your web application faster, shrinking code will also save you money because it reduces
bandwidth costs. Further, it helps protect your IP because renaming variables serves
to obfuscate your code, making it more difficult for other websites to copy your
functionality.

Tools Overview
In addition to the Closure Compiler, there are currently four other tools available in
the Closure suite. Figure 1-1 shows the common workflow when using all of the tools
together. This section provides a brief description of each tool in the order in which it
is encountered in this book.

Figure 1-1. Workflow when using Closure Tools.

Closure Library
The Closure Library is a comprehensive JavaScript library analogous to other contemporary offerings, such as jQuery, Dojo, and MooTools. The coding style and use of
annotations in the Closure Library are tailored for use with the Closure Compiler,
which is its main distinguishing feature when compared to other JavaScript libraries.
2 | Chapter 1: Introduction to Closure

www.it-ebooks.info

This can have dramatic effects on the Compiler’s ability to minify code, as a simple
minification experiment finds that Closure Library code can be 85 percent smaller when
using the Closure Compiler in place of the YUI Compressor (http://blog.bolinfest.com/
2009/11/example-of-using-closure-compiler-to.html).
The Closure Library is also implemented with a strong emphasis on performance and
readability. It is frugal in creating objects, but generous in naming and documenting
them. It also has an elegant event system, support for classes and inheritance, and a
broad collection of UI components, including a rich text editor. Closure Library code
is regularly tested across browsers, and to the extent that it can, will also work in nonbrowser JavaScript environments, such as Rhino (http://www.mozilla.org/rhino/) and
the Microsoft Windows Script Host. Because the Library is a resource for Google engineers first and an open source project second, it is a safe bet that every line of code
in the Library was developed to support at least one Google product. The style of the
Library will first be introduced in Chapter 2, and the functionality of the Library will
be covered in the following eight chapters.

Closure Templates
Closure Templates provide an intuitive syntax for creating efficient JavaScript functions
(or Java objects) that generate HTML. This makes it easier to create a large string of
HTML that can in turn be used to build up the DOM. Unfortunately, most programming languages do not have native support for templates, so creating a separate
templating solution is a common practice for web frameworks (J2EE has JSP, Python
developers frequently use Django’s template system, etc.). A unique aspect of Closure
Templates is that the same template can be compiled into both Java and JavaScript, so
those running servers written in Java (or JavaScript!) can use the same template on both
the server and the client. The benefits of this, along with Closure Templates, will be
covered in Chapter 11.

Closure Compiler
The Closure Compiler is a JavaScript optimizing compiler: it takes JavaScript source
code as input and produces behaviorally equivalent source code as output. That is,
when the output code is used in place of the input code, the observable effect will be
the same (though the output code is likely to execute faster than the original). As a
simple example, if the input code were:
/**
* @param {string} name
*/
var hello = function(name) {
alert('Hello, ' + name);
};
hello('New user');

Tools Overview | 3

www.it-ebooks.info

then the Compiler would produce the following behaviorally-equivalent output:
alert("Hello, New user");

Executing either code snippet will have the same effect: an alert box will display with
the text "Hello, New user". However, the output code is more concise, so it can be
downloaded, parsed, and executed faster than the input code.
Furthermore, the Compiler can detect a large class of errors by performing static checks
at compile time, much like JSLint. This helps find bugs earlier, dramatically speeding
up JavaScript development. Using the Compiler to identify problems is not a substitute
for unit testing, but it certainly helps.
For existing JavaScript applications, the Closure Compiler is likely to be the Closure
Tool that is most immediately useful. Although it will be most effective when used to
compile code written in the style of the Closure Library, replacing an existing dependency on jQuery or Dojo with that of the Library could be time-consuming. By comparison, the Closure Compiler can be used in place of existing JavaScript minifiers (such
as JSMin or YUI Compressor) with much less effort. The Compiler will be introduced
in Chapter 12.

Closure Testing Framework
The Closure Testing Framework is a unit-testing framework that runs in the browser,
much like JsUnit. Most Closure Library code has a corresponding test that runs in the
Framework. It is good programming practice to create tests for your own code and to
run them regularly to identify regressions. Because the Closure Testing Framework runs
inside the browser, additional software is needed to automate the process of starting
up a browser, running the tests, and recording the results. Selenium is likely the best
solution for that purpose. The Closure Testing Framework will be explained in
Chapter 15.

Closure Inspector
The Closure Inspector is an extension to Firebug to aid in debugging compiled JavaScript. Firebug is an extension for Firefox (which is not developed by Google) that
brings together a number of web development tools, including a JavaScript debugger,
available through the browser. When using the Firebug debugger with obfuscated code
produced by the Closure Compiler, it is hard to trace a runtime error back to its position
in the original source code. The Closure Inspector facilitates debugging by exposing
the mapping between the original and compiled code in the Firebug UI. It will be discussed in more detail in Chapter 16.

4 | Chapter 1: Introduction to Closure

www.it-ebooks.info

Closure Design Goals and Principles
Before diving into the code, it is important to understand the design goals and principles
that motivate the implementation of the Closure Tools. Much of the design of the
toolkit is motivated by the capabilities of the Compiler and the style of the Library.

Reducing Compiled Code Size Is Paramount
The primary objective of the Closure Compiler is to reduce the size of JavaScript code.
Because Google serves so many pages with JavaScript and prides itself on speed (Google
engineers have T-shirts that say “Fast is my favorite feature”), it is imperative that the
JavaScript required to display a page is as small as possible. Even when JavaScript is
cached by the browser, it must still be parsed and executed again when the page that
uses it is reloaded. The smaller the JavaScript, the less time this takes.
Specifically, the Compiler favors reducing the size of gzipped JavaScript over uncompressed JavaScript. For example, it might be tempting to have the Compiler rewrite the
following function:
Line.prototype.translate = function(distance) {
this.x1 += distance;
this.y1 += distance;
this.x2 += distance;
this.y2 += distance;
};

so that it creates a temporary variable for this before compiling the code:
Line.prototype.translate = function(distance) {
var me = this;
me.x1 += distance;
me.y1 += distance;
me.x2 += distance;
me.y2 += distance;
};

The motivation here is that the Compiler can rename me but cannot rename this because this is a JavaScript keyword. Although using the temporary variable results in
smaller uncompressed code when run through the Compiler, the gzipped size of the
compiled code is larger when using the temporary variable. Because the overwhelming
majority of browsers can accept gzipped JavaScript, the Compiler focuses on optimizations that will benefit the gzipped code size. Most optimizations are wins for both
compressed and gzipped JavaScript, but there are occasionally exceptions, such as this
one.
JavaScript code should be written in a way that can be compiled efficiently by the
Compiler. This is fundamental to understanding the design of the Closure Library: the
verbosity of the code is not representative of its size after being processed by the Compiler. If more code (or annotations) need to be written to result in smaller compiled
code, then that is preferable to writing less code that results in larger compiled code.
Closure Design Goals and Principles | 5

www.it-ebooks.info

For example, writing comprehensive utility libraries is acceptable as long as the unused
parts can be removed by the Compiler. Complementary methods should be replaced
with a single parameterized method (e.g., prefer setEnabled(enable) to enable() and
disable()). This reduces the number of method declarations and is more amenable to
function currying. Therefore, to fully understand the Closure Library, one must also
understand how the Compiler rewrites JavaScript code.
One may wonder if any emphasis is placed on using the Compiler to produce JavaScript
with better runtime performance. The short answer is yes, but because runtime performance is so much harder to measure than code size, more engineering time has been
spent on improving minification. Fortunately, many reductions in code size also
improve performance, as many optimizations result from evaluating expressions at
compile time rather than runtime.

All Source Code Is Compiled Together
The Compiler is designed to compile all code that could be run during the course of
the application at once. As shown in Figure 1-1, there are many potential sources of
input, but the Compiler receives all of them at the same time. This is in contrast to
other languages, in which portions of source code are compiled into reusable modules.
In Closure, it is the opposite: source code is initially compiled together and is then
carved up into modules that may be progressively loaded by a web application. This is
done to ensure that the variable names used in individual modules are globally unique.

Managing Memory Matters
As the Gmail team explained on their blog (http://gmailblog.blogspot.com/2008/09/new
-gmail-code-base-now-for-ie6-too.html), they encountered a performance problem with
Internet Explorer 6 (IE6) with respect to memory management that prevented IE6 users
from getting a newer version of Gmail until Microsoft provided a patch to IE6 users.
Although this caused the Gmail engineers a considerable amount of pain, it did force
them to invest extra effort into managing memory on the client.
Like most modern programming languages, JavaScript manages its own memory. Unfortunately, this does not preclude the possibility of a memory leak, as failing to release
references to objects that are no longer needed can still cause an application to run out
of memory. The Closure Library uses goog.Disposable to ensure that references are
released as soon as possible so that objects may be garbage collected. goog.Disposa
ble will be introduced in Chapter 5, and managing event listeners (another common
source of memory leaks) will be explained in Chapter 6.
The issues with IE6’s garbage collection are so severe that the Closure Library offers
goog.structs.Map as an abstraction around JavaScript’s native Object to reduce the
number of string allocations when iterating over the keys of an object. The justification
is revealed in a comment in the goog.structs.Map source code:

6 | Chapter 1: Introduction to Closure

www.it-ebooks.info

/**
* An array of keys. This is necessary for two reasons:
*
1. Iterating the keys using for (var key in this.map_) allocates an
*
object for every key in IE which is really bad for IE6 GC perf.
*
2. Without a side data structure, we would need to escape all the keys
*
as that would be the only way we could tell during iteration if the
*
key was an internal key or a property of the object.
*
* This array can contain deleted keys so it's necessary to check the map
* as well to see if the key is still in the map (this doesn't require a
* memory allocation in IE).
* @type {!Array.}
* @private
*/
this.keys_ = [];

Now that Microsoft has provided a patch for the problem with IE6, such
micromanagement of string allocation is less compelling. However, as more mobile
devices are running web browsers with fewer resources than their desktop equivalents,
attention to memory management in general is still merited.

Make It Possible to Catch Errors at Compile Time
The Closure Compiler is not the first tool to try to identify problems in JavaScript code
by performing static checks; however, there is a limit to how much can be inferred by
the source code alone. To supplement the information in the code itself, the Compiler
makes use of developer-supplied annotations which appear in the form of JavaScript
comments. These annotations are explained in detail in Chapter 2.
By annotating the code to indicate the parameter and return types of functions, the
Compiler can identify when an argument of the incorrect type is being passed to a
function. Similarly, annotating the code to indicate which data are meant to be private
makes it possible for the Compiler to identify when the data are illegally accessed. By
using these annotations in your code, you can use the Compiler to increase your confidence in your code’s correctness.

Code Must Work Without Compilation
Although the Compiler provides many beneficial transformations to its input, the code
for the Closure Library is also expected to be able to be run without being processed
by the Compiler. This not only ensures that the input language is pure JavaScript, but
also makes debugging easier, as it is always possible to use the deobfuscated code.

Code Must Be Browser-Agnostic
The Closure Library is designed to abstract away browser differences and should work
in all modern browsers (including IE6 and later). It should also work in non-browser
environments, such as Rhino and the Windows Script Host (though historically the
Closure Design Goals and Principles | 7

www.it-ebooks.info

motivation behind creating a browser-agnostic library was to support WorkerPools in
Google Gears). This means that common browser objects such as window and naviga
tor are not assumed to exist.
This does not mean that the Closure Library lacks utilities for dealing with browserspecific APIs such as the DOM. On the contrary, the Library provides many methods
for working within the browser. However, Library code that works with objects that
are universally available in all JavaScript environments (strings, arrays, functions, etc.)
does not rely on APIs that are available only to the browser. This makes the Closure
Library a good candidate for use with server-side JavaScript, as well.

Built-in Types Should Not Be Modified
Built-in object prototypes, such as Object, Function, Array, and String should not be
modified. This makes it possible to use Closure alongside other JavaScript libraries,
such as jQuery. In practice, however, using Closure with other libraries is generally
inefficient. Each library will have its own logic for event management, string manipulation, etc., which means that duplicate logic will likely be included, increasing the
amount of JavaScript code that will be loaded.

Code Must Work Across Frames
The Closure Library is designed to be loaded once per frameset (though it is designed
so that multiple instances of the Library should not “step on each other” if it is loaded
more than once). The Library recognizes that built-in objects, such as Arrays, may be
constructed in different frames and therefore will have distinct prototypes. For web
applications that use multiple frames (such as using a separate 



A goog.net.IframeLoadMonitor does not dispatch any sort of event to indicate whether
the iframe was loaded successfully: goog.net.IframeLoadMonitor.LOAD_EVENT indicates
only that loading is complete.

goog.net.MultiIframeLoadMonitor
A goog.net.MultiIframeLoadMonitor is like a bulk loader for iframes. Its constructor
takes an array of iframes to monitor, but there is only one opt_hasContent parameter,
so it must apply to all of the iframes. Instead of dispatching an event when all of the
iframes have finished loading, it will call a callback supplied to the constructor. Unfortunately, the callback does not get any arguments, so the client is responsible for
maintaining references to its iframes, rather than calling e.target.getIframe() as it
would for an event dispatched by an goog.net.IframeLoadMonitor:




Monitoring can be cancelled by invoking the monitor’s stopMonitoring() method,
which will dispose of all the underlying goog.net.IframeLoadMonitors that are still
active.

goog.net.NetworkTester
A goog.net.NetworkTester tests for Internet connectivity by trying to load an image
from google.com. Unlike the other classes in goog.net, it does not dispatch an event to
announce a change in connectivity, but takes a callback function as an argument to its
constructor that will be called with a single boolean to indicate whether the image could
be reached:
var networkTester = new goog.net.NetworkTester(function(isOnline) {
var msg = isOnline ? 'I am online - time to catch up on blogs!' :
'I am offline - I guess I will find a book to read.';

Resource Loading and Monitoring | 169

www.it-ebooks.info

alert(msg);
// goog.net.NetworkTester does not extend goog.Disposable, so there is no
// need to dispose of it here.
});
// The start() method must be called to kick off network testing.
networkTester.start();

By default, the network tester will try to load the image at http://www.google.com/
images/cleardot.gif, but with a random parameter appended to it, such as http://
www.google.com/images/cleardot.gif?zx=z9qcpeyltrkr. This random value is added to
ensure that the image is not loaded from the cache. It is possible to specify a different
image URL to load using either the optional opt_uri constructor parameter or the
setUri() method.
Other options for goog.net.NetworkTester include how long to wait for the image to
load (default is 10 seconds), how many times to retry fetching the image (default is 0),
and how long to wait between retries (default is 0 milliseconds). Setter and getter
methods are available for each of these properties, so these options can be configured
before start() is called.
Although goog.net.NetworkTester does not extend goog.Disposable, its stop() method
should be used to clean up its resources to cancel testing, if desired. If testing completes
normally and the callback is called, goog.net.NetworkTester will clean up its own resources, so there is no need to call stop() in that case. Once stop() is called, it is possible
to reuse the tester by calling start() again. It will use the same settings and callback
from when it was originally called, though it will also use the same URL, which could
be fetched from the cache and return a false positive. It is a good idea to call setUri()
with a new value when reusing a goog.net.NetworkTester.

Cross-Domain Communication
All the examples shown so far in this chapter rely on the use of an XHR which restricts
communication to the host that served the web page. This is because XHRs in Closure
are assumed to be same-origin XHRs, which means they abide by the same origin policy.
The same origin policy mandates that a script running on a web page is allowed to access
only resources with the same protocol, domain name, and port of the host page. (The
Level 2 specification for XMLHttpRequest specifies how an XHR may be used to make
cross-origin requests: http://www.w3.org/TR/XMLHttpRequest2/, which some browsers have started to implement.) Therefore, under the same-origin policy, JavaScript
running on http://example.com/mypage.html cannot make an XHR to any of the URLs
in Table 7-2.

170 | Chapter 7: Client-Server Communication

www.it-ebooks.info

Table 7-2. URLs that are considered to have a different origin than http://example.com/mypage.html.
URL

Why access is denied

http://google.com/

Different domain

https://example.com/mypage.html

Different protocol

http://example.com:8080/index.jsp

Different port

http://www.example.com/mypage.html

Different subdomain

This may seem overly restrictive, but there are compelling security issues behind the
same origin policy.

goog.net.jsonp
Although an XHR must obey the same-origin policy, the URL supplied to the src attribute of an  or 


Cross-Domain Communication | 171

www.it-ebooks.info

The trick behind this technique is for the server to accept the name of the callback
function as a GET parameter, and to use that as the “padding” when wrapping the
JSON in the response. This gives the client the flexibility to specify the name of the
function in its own page that will receive the data.
Closure provides a goog.net.Jsonp class to help create the 

Behind the scenes, sendFromForm(form) creates a hidden iframe and uses it as the target
of form to prevent the form submission from adding an entry to the browser’s history
stack. Although this technique is primarily used to manage file uploads, all input values
for the some_form form will be submitted when sendFromForm(goog.dom.getElement
('some_form')) is called.
Note that it is possible for the server code that handles the upload to write a response
that can be read from the goog.net.IframeIo. For example, if it wrote out a string of
JSON, the response could be read in the onComplete() handler as follows:
var onComplete = function(e) {
var iframeIo = /** @type {goog.net.IframeIo} */ (e.target);
try {
var json = iframeIo.getResponseJson();
// Use the JSON: perhaps it contains information about the
// content of the file that should be displayed to the user.
} catch (e) {
// The server did not write valid JSON.
}
fileInput.disabled = false;
iframeIo.dispose();
};

Note that if this technique is used, the Content-Type of the response from the server
should be text/plain. If text/javascript is used, Internet Explorer will prompt the
user to download the file rather than display its content in the hidden 

392 | Chapter 13: Advanced Compilation

www.it-ebooks.info

As a reminder, the javascript: protocol executes the JavaScript code to
the right of the colon and uses the result as the content of the page. In
this case, the code will create an iframe with an HTML document in
standards mode whose DOM can be modified by the parent page. A
more traditional approach to get an iframe with an empty document is
to set the src attribute to about:blank, but that results in a page rendered
using quirks mode. Because the rendering mode of a page cannot be
changed once it is loaded, the content of the iframe must have the correct
doctype when it is created.

Because the Compiler will rename example.generateStandardsModeHtmlDoc() in Advanced mode, the code inlined in the src attribute of the iframe will fail because it refers
to the original name of the function, which is not available in the compiled code. The
simplest solution is to export the function using either goog.exportSymbol() or
goog.exportProperty(), which were introduced in Chapter 3. Note that it is not required for the exported name to be the same as the original name:
goog.exportSymbol('generateHtml', example.generateStandardsModeHtmlDoc);

which makes it possible to shorten the HTML:


After creating an export, it may be tempting to use the shorter, exported name in your
JavaScript code, but this is a mistake. Although it will work in uncompiled code, a call
to generateHtml() within your own code will result in a Compiler warning or error
because the Compiler cannot find a function declaration for generateHtml. To be able
to use an exported value at all in compiled code, it must be referred to by its name as
a quoted string, which cannot be compressed by the Compiler:
// This works in uncompiled mode, but it will not pass the compile time
// checks of the Compiler in Advanced mode because it will complain that the
// variable 'generateHtml' is undefined, despite the call to goog.exportSymbol():
var html1 = generateHtml();
// Using the exported name in a Compiler-safe way:
var html2 = goog.global['generateHtml']();
// Using the internal name:
var html3 = example.generateStandardsModeHtmlDoc();

Like so many other things in Closure, even though example.generateStandardsModeHtml
Doc() appears to be more expensive in terms of code size as compared to using
goog.global['generateHtml'], that is not the case after compilation.
Because goog.exportSymbol() and goog.exportProperty() define new values on the
global object, it is even more important to use them when your code is wrapped in an
anonymous JavaScript function. For example, the following will not work even in uncompiled mode:

What Happens During Compilation | 393

www.it-ebooks.info




Because of the anonymous function wrapper, example.handleClick() is not defined in
the top-level scope, which is the only scope that DOM-0 event handlers (such as the
inline onclick attribute) have access to. Adding goog.exportSymbol('example.handle
Click', example.handleClick); after the definition of example.handleClick would solve
this problem, although the best solution is to avoid DOM-0 event handlers altogether
and use Closure’s event system:



This approach is preferable to using exports for several reasons:
• The code can be compiled more efficiently because it no longer contains the uncompressible example.handleClick string. The HTML is also shorter because the
onclick attribute has been dropped.
• It avoids the possibility of misspelling example.handleClick in either the HTML or
the JavaScript, neither of which can be caught by the Compiler.

394 | Chapter 13: Advanced Compilation

www.it-ebooks.info

• The event handler gets a Closure event rather than a native event, which is much
easier to work with across web browsers.
This does not mean that exports should never be used: as shown in the iframe example
with the javascript: URI, sometimes there is no alternative to using inlined JavaScript
in HTML. However, for DOM-0 event handlers, the Closure event system is a superior
alternative.

Exporting methods for a public API
When creating a compiled JavaScript library that is meant to be included on another
web page, like the Google Maps API, all public members of the API will need to be
exported. The pattern for creating such a library is as follows:
• Write library code as would ordinarily be done in Closure.
• Create a separate JavaScript file that declares all of the exported functions, using
goog.require() calls, as appropriate.
• Compile the JavaScript file with the exports and use (function(){%output%})();
as an output wrapper.
As an example, assume that the library code for a linkifier is in a file named example/
linkifier.js:
goog.provide('example.linkifier');
goog.require('goog.dom');
goog.require('goog.dom.NodeType');
goog.require('goog.string');
/**
* @param {!Node} node
* @return {boolean}
*/
example.linkifier.isAnchor = function(node) {
return (node.nodeType == goog.dom.NodeType.ELEMENT &&
node.nodeName == 'A');
};
/**
* @type {RegExp}
* @const
*/
example.linkifier.urlPattern = /https?:\/\/[\S]+/g;
/** @param {!Node} node A text node */
example.linkifier.linkifyNode = function(node) {
var text = node.nodeValue;
var matches = text.match(example.linkifier.urlPattern);
if (!matches) {
return;
}

What Happens During Compilation | 395

www.it-ebooks.info

var html = [];
var lastIndex = 0;
for (var i = 0; i < matches.length; ++i) {
var match = matches[i];
var index = text.indexOf(match, lastIndex);
var escapedMatch = goog.string.htmlEscape(match);
html.push(goog.string.htmlEscape(text.substring(lastIndex, index)),
'', escapedMatch, '');
lastIndex = index + match.length;
}
html.push(goog.string.htmlEscape(text.substring(lastIndex)));
var fragment = goog.dom.htmlToDocumentFragment(html.join(''));
goog.dom.replaceNode(fragment, node);

};

/** @param {!Node} node That is not an ancestor of an anchor element. */
example.linkifier.expandNode = function(node) {
if (!example.linkifier.isAnchor(node)) {
if (node.nodeType == goog.dom.NodeType.TEXT) {
example.linkifier.linkifyNode(node);
} else {
for (var n = node.firstChild; n; n = n.nextSibling) {
example.linkifier.expandNode(n);
}
}
}
};
/**
* @param {Node} node
* @return {boolean} whether node is a descendant of an anchor element
*/
example.linkifier.hasAnchorAncestor = function(node) {
while (node) {
if (example.linkifier.isAnchor(node)) {
return true;
}
node = node.parentNode;
}
return false;
};
/** @param {Node} node The node to linkify. */
example.linkifier.linkify = function(node) {
if (node && !example.linkifier.hasAnchorAncestor(node)) {
example.linkifier.expandNode(node);
}
};

And the public API were declared in exports/linkify.js:
goog.require('example.linkifier');
goog.exportSymbol('mylib.linkify', example.linkifier.linkify);

Then the command to build the library would be:
396 | Chapter 13: Advanced Compilation

www.it-ebooks.info

python ../closure-library/closure/bin/calcdeps.py \
--path ../closure-library \
--path example \
--input exports/linkify.js \
--compiler_jar ../closure-compiler/build/compiler.jar \
--output_mode compiled \
--compiler_flags "--compilation_level=ADVANCED_OPTIMIZATIONS" \
--compiler_flags "--output_wrapper=(function(){%output%})();" \
> linkified-library.js

The compiled library looks something like:
(function(){var d=true,f=this;function g(a,b,c){a=a.split(".");c=c||f...

Even though there are lots of variables that have been renamed, none of them pollute
the global namespace because they are wrapped in the anonymous function that was
specified using the --output_wrapper. To see the library in action, test it with the following web page:



Linkify Test




http://www.google.com/

blah http://www.example.com:8080/fake?foo=bar#yes! blah As expected, alert(typeof d) displays 'undefined' because var d=true is local to the library and does not create a top-level variable named d. Further, the library works as expected: it does not interfere with the existing link to Google, but it does linkify the crazy URL to example.com without including the blah text that is not part of the URL. This approach creates an efficiently compiled library that can safely be included on third-party websites. The only variable that will be added to the global namespace is mylib.linkify, as desired. Also, because the call to goog.exportSymbol() is made in exports/linkify.js rather than inside example/linkifier.js, it is also possible to use the linkification library without exporting its symbols. For example, when Google Maps uses its JavaScript to display a map on maps.google.com, there is no reason to include the exports for the Google Maps API because that would just add unnecessary bytes to the download. By keeping the exports separate, it is easy to provide a thirdparty library while keeping the core library lean. One important exception to this practice of separating library code from export declarations is for exports that are required to ensure the correctness of the code. For example, the export used with the javascript: URI in the iframe example in the previous section is mandatory if the library is designed to insert an iframe on the page that What Happens During Compilation | 397 www.it-ebooks.info calls back into the library by using a javascript: URI in the source of the iframe. The following example shows how this technique can be used to draw a widget into a page that requires standards mode, even if the parent page is in quirks mode: goog.provide('example'); goog.require('goog.dom'); /** @param {Element} el */ example.drawWidgetIntoElement = function(el) { el.innerHTML = ''; }; example.generateStandardsModeHtmlDoc = function() { return '' + '' + '' + '' + ''; }; goog.exportSymbol('example.createDoc', example.generateStandardsModeHtmlDoc); /** @param {Window} win from the newly created document */ example.onIframeLoad = function(win) { // This is the document of the newly created iframe, so it is possible to // manipulate its DOM from here. var doc = win.document; }; goog.exportSymbol('example.onIframeLoad', example.onIframeLoad); In this example, both the iframe and its document rely on calling functions in the parent window. Without the calls to goog.exportSymbol(), this code would not work at all, so they should be included as part of the library code rather than in a separate exports file. Although it is slightly slower to create an iframe and draw into it than it is to draw into an existing

on the parent page, this technique makes it possible to guarantee that a widget will be rendered in standards mode, even when the host page is in quirks mode. It also has the advantage that a stylesheet needed for the widget can be loaded only in the iframe, so the styles for the widget are guaranteed not to interfere with the CSS on the parent page. This makes it much simpler to maintain a widget that is designed to be embedded on an arbitrary third-party site. Note how a client of the compiled linkification library who was using the Compiler in Advanced mode would need to declare mylib.linkify as an extern in order to compile against it. It is indeed the case that the exports for the owner of a library are often the same as the externs needed by a client of that library. (Again, example.createDoc() and example.onIframeLoad() would be exceptions to this rule because they are exported for the correctness of the code, not to support a public API.) For this reason, the Closure Compiler supports an option for generating an externs file from calls to goog.export 398 | Chapter 13: Advanced Compilation www.it-ebooks.info Symbol() and goog.exportProperty() in the input code. This option is available only via the Java API, as explained in “externExports” on page 449. One final reminder on exports is that, as shown in Chapter 3 in the section on goog.exportProperty(), exporting mutable properties is problematic. Therefore, mutable properties should be exported via getter and setter methods rather than exporting them directly. Consider a class that represents a poll that keeps a tally of yea and nay votes via mutable properties: goog.provide('example.Poll'); /** * @param {number=} yea Defaults to 0. * @param {number=} nay Defaults to 0. */ example.Poll = function(yea, nay) { if (goog.isDef(yea)) this.yea = yea; if (goog.isDef(nay)) this.nay = nay; }; goog.exportSymbol('Poll', example.Poll); /** @type {number} */ example.Poll.prototype.yea = 0; goog.exportProperty(Poll.prototype, 'yea', Poll.prototype.yea); /** @type {number} */ example.Poll.prototype.nay = 0; goog.exportProperty(Poll.prototype, 'nay', Poll.prototype.nay); /** @return {string} */ example.Poll.prototype.toString = function() { var total = this.yea + this.nay; if (total == 0) return 'No votes yet!'; return (100 * this.yea / (this.yea + this.nay)) + '%'; }; When compiled in Advanced mode, the result would be something like the following: var a = function(b, c) { if (b !== undefined) this.d = b; if (c !== undefined) this.e = c; }; var Poll = a; a.prototype.d = 0; a.prototype.x = a.prototype.d; a.prototype.e = 0; a.prototype.y = a.prototype.e; a.prototype.toString = function() { var b = this.d + this.e; if (b == 0) return 'No votes yet!'; return (100 * this.d / b) + '% in favor'; }; What Happens During Compilation | 399 www.it-ebooks.info Suppose the following code were used with the compiled version of example.Poll: var poll = new Poll(); poll.yea = 3; poll.nay = 1; // This would display 'No votes yet!' instead of '75% in favor'. alert(p.toString()); This could be solved by providing appropriate getters and setters for yea and nay and removing the exports to Poll.prototype.yea and Poll.prototype.nay. Here are the sample getters and setters for yea: example.Poll.prototype.getYea = function() { return this.yea; }; goog.exportProperty(Poll.prototype, 'getYea', Poll.prototype.getYea); example.Poll.prototype.setYea = function(yea) { this.yea = yea; }; goog.exportProperty(Poll.prototype, 'setYea', Poll.prototype.setYea); and what they would look like after compilation: a.prototype.f = function() { return this.d; }; a.prototype.getYea = a.prototype.f; a.prototype.g = function(h) { this.d = h; }; a.prototype.setYea = a.prototype.g; Then the client code would become: var p = new Poll(); p.setYea(3); p.setNay(1); // Now this displays '75% in favor' as desired. alert(p.toString()); Be sure to keep this in mind when designing a public API for library code that is going to be compiled in Advanced mode. Property Flattening As discussed in Chapter 3, because of the way objects are used as namespaces in Closure, function calls incur the additional overhead of property lookups on namespace objects before actually calling the function. Fortunately, this cost is eliminated by the Compiler in Advanced mode by an optimization known as property flattening. Understanding how property flattening (or collapsing) works is important because it will influence how you write code that can be compiled efficiently be the Compiler. For example, when using several functions from the goog.string namespace, it might be tempting to write: // // // // ==ClosureCompiler== @compilation_level ADVANCED_OPTIMIZATIONS @use_closure_library true ==/ClosureCompiler== 400 | Chapter 13: Advanced Compilation www.it-ebooks.info goog.require('goog.string'); var formatMessageBoardPost = function(gs, user, message, signature) { return '' + gs.trim(user) + '
' + gs.htmlEscape(message) + '
' + gs.htmlEscape(gs.trim(message)); }; alert(formatMessageBoardPost(goog.string, 'bolinfest', 'hello world', 'http://www.example.com/')); instead of: // // // // ==ClosureCompiler== @compilation_level ADVANCED_OPTIMIZATIONS @use_closure_library true ==/ClosureCompiler== goog.require('goog.string'); var formatMessageBoardPost = function(user, message, signature) { return '' + goog.string.trim(user) + '
' + goog.string.htmlEscape(message) + '
' + goog.string.htmlEscape(goog.string.trim(message)); }; alert(formatMessageBoardPost('bolinfest', 'hello world', 'http://www.example.com/')); You might be inclined to think that the first example will run faster because the lookup of string on goog is done only once instead of four times; however, it turns out that the first example is slower and results in compiled code nearly 10 times as large as the second example. The ==ClosureCompiler== annotation is included so you can run this in the Closure Compiler UI to see the result for yourself. Understanding property flattening is the key to identifying the source of the dramatic difference in compiled code size. When property collapsing is enabled, as it is in Advanced mode, the Compiler internally converts a property chain like this: goog.string.trim = function(str) { /* ... */ }; to a single variable like this: var goog$string$trim = function(str) { /* ... */ }; Now goog$string$trim is a variable name that can be renamed just like any other variable in the application. Also, because it is a simple function declaration, it can also be inlined. In effect, the second example depends on two variables from goog.string: goog $string$trim and goog$string$htmlEscape. Everything else in goog.string is omitted from the compiled output. By comparison, the first example uses goog.string as a variable, creating a dependency on the entire goog.string namespace, so the compiled output is 10 times as large What Happens During Compilation | 401 www.it-ebooks.info because it now includes all of goog.string. Because of the way in which goog. string.trim is aliased to gs.trim, the Compiler is unable to inline the calls to gs.trim in the first example, whereas it is able to inline them in the second example, improving runtime performance. Fortunately, the Compiler provides a warning to indicate this error: JSC_UNSAFE_NAMESPACE: incomplete alias created for namespace goog.string at line 8 character 29 alert(formatMessageBoardPost(goog.string, 'bolinfest', 'hello world', ^ It is important to realize that even doing a proper aliasing is somewhat wasteful: var trim = goog.string.trim; var htmlEscape = goog.string.htmlEscape; This just introduces extra variable declarations, which add more bytes. After variable renaming, the new name for the local htmlEscape variable is likely to be as long as the new name for goog$string$htmlEscape, so there is no expected benefit with respect to getting a shorter function name by redeclaring the variable. Note that property flattening does not extend beyond the prototype property, if it exists. Consider what would happen to instance methods of the following class, example.Lamp: goog.provide('example.Lamp'); /** @constructor */ example.Lamp = function() {}; example.Lamp.prototype.isOn_ = false; example.Lamp.prototype.turnOn = function() { this.isOn_ = true; }; example.Lamp.prototype.turnOff = function() { this.isOn_ = false; }; var lamp = new example.Lamp(); lamp.turnOn(); If all properties were collapsed, the previous code would be compiled to the following, which would fail: var example$Lamp = function() {}; var example$Lamp$prototype$isOn_ = false; var example$Lamp$prototype$turnOn = function() { this.isOn_ = true; }; var example$Lamp$prototype$turnOff = function() { this.isOn_ = false; }; var lamp = new example$Lamp(); // This throws an error because there is no property named "turnOn" defined // on the prototype of example$Lamp. lamp.turnOn(); Fortunately, the Compiler does the right thing by limiting the property flattening: var example$Lamp = function() {}; 402 | Chapter 13: Advanced Compilation www.it-ebooks.info example$Lamp.prototype.isOn_ = false; example$Lamp.prototype.turnOn = function() { this.isOn_ = true; }; example$Lamp.prototype.turnOff = function() { this.isOn_ = false; }; var lamp = new example$Lamp(); lamp.turnOn(); // Now this works as expected. The Compiler actually goes one step further by creating an intermediate variable for example$Lamp.prototype and defining properties on that: var example$Lamp = function() {}; var example$Lamp$prototype = example$Lamp.prototype; example$Lamp$prototype.isOn_ = false; example$Lamp$prototype.turnOn = function() { this.isOn_ = true; }; example$Lamp$prototype.turnOff = function() { this.isOn_ = false; }; This results in smaller compiled code because the prototype property cannot be renamed, but example$Lamp$prototype can. It is important to recognize that property flattening affects how this will be resolved when a function is called. Consider the following code, which works fine in uncompiled mode: example.Lamp.masterSwitch_ = {}; example.Lamp.getMasterSwitch = function() { return this.masterSwitch_; }; example.Lamp.getMasterSwitch(); When example.Lamp.getMasterSwitch() is called, example.Lamp is the receiver, so it will be used in place of this when the function is called. Now consider how this code snippet is transformed as a result of property flattening: example$Lamp$masterSwitch_ = {}; example$Lamp$getMasterSwitch = function() { return this.masterSwitch_; }; example$Lamp$getMasterSwitch(); Now when example$Lamp$getMasterSwitch() is called, its receiver is the global object. Unfortunately, there is no masterSwitch_ property defined on the global object, so the function will return undefined. Further, because example$Lamp$masterSwitch_ is never referenced in the input, it will be removed by the Compiler. Fortunately, compiling this code in Advanced mode yields the following warning from the Compiler: JSC_UNSAFE_THIS: dangerous use of this in static method example.Lamp.getMasterSwitch at line 6 character 7 What Happens During Compilation | 403 www.it-ebooks.info return this.masterSwitch_; ^ Recall that functions that are deliberately designed to use this from what appears to be a static method should use the @this annotation to eliminate the Compiler warning: /** @this {Element} */ example.removeElement = function() { this.parentNode.removeChild(this); }; Property Renaming Property renaming is another type of renaming optimization done by the Compiler that focuses on saving bytes by using shorter names for properties. This has a significant impact on how properties are referenced in your code, so it is important to understand the nuances of this optimization, or else you may end up with bugs that are difficult to track down. Given an object with several properties: var jackal = { numLegs: 4, isPredator: true, "found in": ['Africa', 'Asia', 'Europe' ] }; There are principally two ways to access a property on the object: as a quoted string or using the property name: var isPredator1 = jackal['isPredator']; var isPredator2 = jackal.isPredator; // access via a quoted string // access via the property name Ordinarily, either access method is equivalent in JavaScript, though this is not the case when using the Advanced mode of the Closure Compiler. In Advanced mode, access via a string implies the property cannot be renamed: the Compiler will never modify the value of a string literal (though it may change how it is expressed to use a more compact character escaping). Though in the case of a property access, it will change the syntax from a quoted string to a property name when possible during compilation in order to save bytes: // var howManyLegs = jackal['numLegs']; is compiled to the following, saving // three bytes: var howManyLegs = jackal.numLegs; // var whereAt = jackal['found in']; is unchanged after compilation because // 'found in' cannot be converted to a property because it contains a space: var whereAt = jackal['found in']; By comparison, access via the property name implies that the property can be renamed, which is exactly what the Compiler will try to do. This means that if references to a property are inconsistent, some will get renamed by the Compiler and others will not, causing an error. Note that consistency must be maintained on a per-property basis 404 | Chapter 13: Advanced Compilation www.it-ebooks.info rather than a global basis. For example, the following code will compile correctly in Advanced mode: var thanksWikipedia = 'A jackal has ' + jackal.numLegs + ' legs, ' + 'and can be found in any of: ' + jackal['found in'].join() + '.'; Even though numLegs and found in are referenced in a different manner, each is consistent with the original declaration in jackal, so there is no problem. This optimization results in significant savings when you consider that every class definition in Closure is an object (the prototype of the constructor function) with many properties defined on it (each instance method is a property). Going back to exam ple.Lamp from the previous section, this is what the class definition could look like after property renaming: var example$Lamp = function() {}; var example$Lamp$prototype example$Lamp$prototype.a = example$Lamp$prototype.b = example$Lamp$prototype.c = = example$Lamp.prototype; false; function() { this.a = true; }; function() { this.a = false; }; var lamp = new example$Lamp(); lamp.b(); The Compiler also has logic to remove unused prototype properties, which works like dead code elimination. In this case, only the method b is used, which depends on a, so c will be eliminated because it is never referenced. Once these results are combined with variable renaming, the compression results are even more impressive: var d = function() {}; var e = d.prototype; e.a = false; e.b = function() { this.a = true; }; var f = new d(); f.b(); Clearly, using unquoted properties makes it possible for the Compiler to dramatically reduce the size of compiled code. For this reason, you may be inclined to use unquoted properties exclusively in your code, but there are likely to be cases where the name of the property is significant and therefore must be quoted so it is preserved by the Compiler. A simple example of this is when specifying a set of properties to use when opening a pop up using window.open(): var options = { 'width': 800, 'height': 600, 'resizable': true }; What Happens During Compilation | 405 www.it-ebooks.info var optionsArray = []; for (var key in options) { optionsArray.push(key + '=' + options[key]); } window.open('http://www.example.com/', 'popup', optionsArray.join()); If 'width', 'height', and 'resizable' were unquoted and renamed to a, b, and c, then this code would not work because those are not valid options in the string of window features passed to window.open(). In this particular case, even if width and height were not quoted, they would not be renamed in Advanced mode because they are declared as properties of several types listed in the default externs. However, the resizable property must include quotes to prevent it from being renamed. Another common case where quoted properties are used is when accessing properties of a JSON object that has been sent from the server and deserialized on the client: var jsonFromServer = '{"height": 6, "weight": 200}'; var personJson = goog.json.parse(jsonFromServer); // The height and weight properties must be referenced using quoted strings // so they match the hardcoded values in the JSON after compilation. var data = 'H=' + personJson['height'] + ';W=' + personJson['weight']; If these values are going to be referenced frequently, it is better to extract the values from the JSON and assign them to local variables that can be renamed by the Compiler: var height = personJson['height']; var weight = personJson['weight']; By using the local height and weight variables throughout the code, the uncompressible height and weight properties will appear only once in the compiled output instead of once per access, saving bytes. Preparing Code for the Compiler Now that it is clear what sort of modifications the Compiler will make to your code in Advanced mode, you should have a better idea of how your code must be written to satisfy the Compiler. Nevertheless, this section lists some additional rules to follow when writing code for Advanced mode. Google maintains its own list of restrictions imposed by the Closure Compiler at http://code.google.com/closure/compiler/docs/limi tations.html. Input Language Instead of formally defining the JavaScript grammar that the Compiler accepts, the documentation simply declares that the Compiler recognizes only input that complies 406 | Chapter 13: Advanced Compilation www.it-ebooks.info with “ECMAScript 262 revision 3” (ES3). In layman’s terms, that is the common subset of JavaScript that works on all modern web browsers, including Internet Explorer 6. This means that Firefox-specific features such as the const keyword, the for each construct, and support for the trailing comma in object literals are not supported by the Compiler. This also means that the host of new language features introduced in revision 5 of the standard (ES5, which was approved January 2010) are not supported yet in the Compiler, so statements such as "use strict" will not cause the Compiler to check whether its input confirms to ES5 strict mode. Because the Compiler is under active development by Google, it is likely that it will eventually support ES5, though it would be more compelling if it could rewrite ES5 code as ES3 so that those who want to develop in ES5 can still run their code in older web browsers. Programmatic Evaluation of Strings of JavaScript Code The previous chapter demonstrated how a call to eval() that worked before compilation may no longer work after compilation in Simple mode, due to local variable renaming. It should come as no surprise that with all of the aggressive renaming done in Advanced mode, calls to eval() are even more brittle. Bear in mind that eval() is not the only function in JavaScript that can evaluate a string of JavaScript: var alertFahrenheitInCelsius = function(f) { alert((f - 32) * 5/9); }; eval('alertFahrenheitInCelsius(212)'); setTimeout('alertFahrenheitInCelsius(32)', 5 * 1000); setInterval('alertFahrenheitInCelsius(451)', 10 * 1000); var f = new Function('x', 'alertFahrenheitInCelsius(x)'); f(911); Each of these examples will work in uncompiled mode, but do not work at all when compiled in Advanced mode because alertFahrenheitInCelsius will get renamed (or removed altogether if the Compiler cannot find any other code that calls it). The rule of thumb is to avoid evaluating strings of JavaScript in any form when using Closure. There are two major exceptions to this rule. The first is using eval() to evaluate a string of trusted JSON, though when using the Closure Library, using goog.json.parse or goog.json.unsafeParse is more appropriate, as explained in Chapter 4. As more browsers implement the fifth edition of the ECMAScript specification, which provides a built-in JSON.parse() function, it should be possible to eliminate the use of eval() altogether in most applications. The second exception is using eval() to preserve conditional comments that would otherwise be stripped by the Compiler, such as: Preparing Code for the Compiler | 407 www.it-ebooks.info var isInternetExplorer = eval('/*@cc_on!@*/false'); Conditional comments are a feature unique to Internet Explorer, primarily used for browser and version number detection. When using the Closure Library, using the goog.userAgent library is preferred. Never Use the with Keyword The with keyword is perhaps one of the most reviled features of JavaScript. Although it has not been deprecated, its use produces a syntax error in ES5 strict mode. Although with is part of ES3 and is supported in all modern web browsers, it is not supported by the Compiler because it alters the rules for variable resolution, which wreaks havoc on the Compiler’s algorithms for variable renaming. The Compiler issues a warning whenever with is used, except in Whitespace Only mode. Checks Provided by the Compiler The Compiler is able to perform additional static checks on the input code that can be specified only by passing command-line flags to the Closure Compiler Application. Although many of these checks can be used on code compiled in Simple mode, they are generally used only on code that is written to comply with Advanced mode because many of them rely on JSDoc annotations. The complete list of these checks is available on the Closure Compiler wiki: http://code.google.com/p/closure-compiler/wiki/ Warnings. Type Checking One of the most interesting features of the Closure Compiler is its ability to perform static type checking, which helps catch a large class of errors at compile time. Even though you are probably tired of typing @param and @return annotations, now it is finally going to pay off! It may be worth reviewing the section on type expressions in Chapter 2 before proceeding with this section so that if you have type errors, you are sure that you have formatted your type expressions correctly. Each of the following JavaScript statements contains an error that could be caught by the Compiler when type checking is enabled: goog.string.repeat(10, 'Are we there yet?'); goog.dom.classes.has(document.body, 'this-class', 'that-class'); var array = new []; var doubleClickEvent = goog.events.EventType.DBL_CLICK; document.body.removeAll(); var point = goog.math.Coordinate(3, 4); To report type checking issues as errors, pass the following flag to the Closure Compiler Application: --jscomp_error=checkTypes 408 | Chapter 13: Advanced Compilation www.it-ebooks.info Using the Verbose warning level will also check for type errors, though not quite as many as checkTypes, but it produces warnings for other non-type-related issues that checkTypes does not: --warning_level=VERBOSE It is considered best practice to use both flags to maximize the number of checks performed by the Compiler. On the error-riddled input example code shown previously, the Compiler finds the following errors when applying both flags in Advanced mode: sample-code.js:1: ERROR - actual parameter 1 of goog.string.repeat does not match formal parameter found : number required: string goog.string.repeat(10, 'Are we there yet?'); ^ sample-code.js:1: ERROR - actual parameter 2 of goog.string.repeat does not match formal parameter found : string required: number goog.string.repeat(10, 'Are we there yet?'); ^ sample-code.js:2: ERROR - Function goog.dom.classes.has: called with 3 argument(s). Function requires at least 2 argument(s) and no more than 2 argument(s). goog.dom.classes.has(document.body, 'this-class', 'that-class'); ^ sample-code.js:3: ERROR - cannot instantiate non-constructor var array = new []; ^ sample-code.js:4: ERROR - element DBL_CLICK does not exist on this enum var doubleClickEvent = goog.events.EventType.DBL_CLICK; ^ sample-code.js:5: ERROR - Property removeAll never defined on HTMLDocument.prototype.body document.body.removeAll(); ^ sample-code.js:6: ERROR - Constructor function (this:goog.math.Coordinate, (number|undefined), (number|undefined)): ? should be called with the "new" keyword var point = goog.math.Coordinate(3, 4); ^ The output contains multiple errors, each of which contains a descriptive message and identifies the source of the error with the filename, line number, and character offset where the error occurred. Running the Compiler frequently during development helps identify errors like these right away so they can be fixed immediately rather than lurking in the codebase, making them harder to track down later. Checks Provided by the Compiler | 409 www.it-ebooks.info None of the errors listed are reported when type checking is disabled, so the advantages of enabling type checking should be clear. But enabling type checking is only part of the solution: most of these errors are able to be identified only because the variables referenced in sample-code.js are annotated with type information when they are initially declared in the Closure Library. One of the major benefits of using the Closure Library with the Closure Compiler, as opposed to another JavaScript library, is that the Library code is already annotated with type information for the Compiler. Not only does this save hours and hours of work, but it also provides countless examples of how to properly annotate your code. Note that the externs files bundled with the Compiler also contain type annotations, as they are also used in type checking, as shown by the error for document.body.removeAll(). The idea behind type checking is fairly simple: you document the types of the inputs and outputs of your functions, and the Compiler complains when the wrong types are passed in or returned. The Compiler will also complain if the number of arguments passed to a function, also known as its arity, is incorrect. Recall that a = at the end of a type expression indicates an optional parameter, and a ... at the start of a type expression indicates a variable number of parameters. These annotations should be used to define the arity of a function when it is a range: /** * @param {string} needle * @param {...string} haystack * @return {boolean} */ var isOneOf = function(needle, haystack) { for (var i = 1; i < arguments.length; i++) { if (needle === arguments[i]) { return true; } } return false; }; // These calls are OK because there is at least one argument: isOneOf('foo'); isOneOf('foo', 'bar', 'baz'); // But this one will elicit a type checking error from the Compiler because // the first argument, needle, is not annotated as being optional: isOneOf(); /** * @param {string} name * @param {string=} title */ var greet = function(name, title) { alert('Greetings ' + (goog.isDef(title) ? title + ' ' : '') + name); }; // These calls are OK because there are one or two arguments: greet('Mother'); greet('Livingstone', 'Dr.'); 410 | Chapter 13: Advanced Compilation www.it-ebooks.info // But this one will elicit a type checking error from the Compiler because // there are more than two arguments: greet('Jon', 'Bon', 'Jovi'); One interesting point about the greet example is that it would likely be more intuitive if the order of the arguments were switched: /** * @param {string=} title * @param {string} name */ var greet = function(title, name) { alert('Greetings ' + (goog.isDef(title) ? title + ' ' : '') + name); }; But recall from Chapter 2 that optional arguments cannot appear before required arguments. When type checking is enabled, the Compiler issues a warning for this sort of code: sample-code.js:5: WARNING - optional arguments must be at the end var greet = function(title, name) { ^ As demonstrated by the erroneous call to goog.string.repeat(10, 'Are we there yet?'), the Compiler can determine when the wrong type of argument is being passed to a function. One special case of this type of error is passing a supertype in place of a subtype: // This results in an error when type checking is enabled. goog.style.getPageOffset(document.body.firstChild); The problem here is that, as it is defined in w3c_dom1.js, the type of firstChild is Node, but goog.style.getPageOffset takes an argument of type Element, which is a subtype of Node. If you designed the DOM of the page in such a way that you are certain that document.body.firstChild will be an Element, you can use an @type annotation as follows to indicate this to the Compiler, which will eliminate the warning: var el = /** @type {Element} */ (document.body.firstChild); goog.style.getPageOffset(el); In this example, the Compiler assumes that el is of type Element. (This is somewhat analogous to a cast in C.) When used in this way, the expression that the @type annotation is referring to must be wrapped in parentheses. Like the annotation itself, the parentheses are necessary only as part of the syntax of the annotation—they will not be included in the compiled output. Note that it is not required to create an intermediate variable to use this annotation: goog.style.getPageOffset(/** @type {Element} */ (document.body.firstChild)); Because it is common to have a reference to a Node that is actually an Element, some library functions claim to accept a Node when they work only on an Element just to avoid the client from having to use lots of @type annotations when using the library. For Checks Provided by the Compiler | 411 www.it-ebooks.info example, the functions in the goog.dom.classes library take Nodes for exactly this reason, even though CSS classes can only be manipulated on Elements. Another common use of the inline @type annotation is to indicate that a reference is non-null when passing it to a function that does not accept null: /** * Recall that the ! indicates that el must be non-null. * @param {!Element} el * @return {Node} */ var getParent = function(el) { return el.parentNode; }; // This results in a type checking error because goog.dom.getElement returns // {Element|null} but getParent only accepts {Element}. var parent = getParent(goog.dom.getElement('footer')); // This error can be eliminated with the appropriate annotation: var parent2 = getParent(/** @type {!Element} */ ( goog.dom.getElement('footer'))); Adding such an @type annotation makes it explicit that you are making an assumption about the input or output of a function. Another way to capture this information is to wrap the original function in a new function with a stronger contract: /** * @param {string|Element} idOrElement * @return {!Element} * @throws {Error} if idOrElement does not identify an element. */ var definitelyGetElement = function(idOrElement) { var el = goog.dom.getElement(idOrElement); if (goog.isNull(el)) { throw Error('No element found for: ' + idOrElement); } return el; }; var parent3 = getParent(definitelyGetElement('footer')); The Compiler’s type system is able to determine that if the return statement is reached, then el must not be null, because if el were null, then the Error would have been thrown and the return statement would have been unreachable. This is why the Compiler accepts the return type of {!Element} even though there is no @type annotation on el. Although this approach results in more code than using the @type annotation, it has the benefit that if the assumption that there is an element in the page with the id footer is violated, the code will fail fast with a descriptive error message. This will help identify the incorrect assumption so it can be repaired. 412 | Chapter 13: Advanced Compilation www.it-ebooks.info Unknown type warnings When type checking is enabled, it will also display warnings for unknown types. This is primarily used to detect spelling mistakes in type expressions, though sometimes these warnings are false positives. Consider the following example: goog.require('goog.events'); goog.require('goog.events.EventType'); /** @param {goog.events.Event} e */ var listener = function(e) { alert(e); }; goog.events.listen(document.body, goog.events.EventType.CLICK, listener); When compiled, more than a dozen warnings appear: goog\events\events.js:126: WARNING - Parse error. Unknown type goog.events.EventTarget * @param {EventTarget|goog.events.EventTarget} src The node to listen to ^ goog\events\events.js:237: WARNING - Parse error. Unknown type goog.events.EventTarget * @param {EventTarget|goog.events.EventTarget} src The node to listen to ^ This happens because this code depends on goog.events, but there is no transitive dependency on goog.events.EventTarget, so goog/events/eventtarget.js is not included in the compilation. Because of this, the Compiler never sees a declaration for goog.events.EventTarget, so it assumes its appearance in the type expression within an @param tag in goog/events.js is an error. The solution is to create a separate file named deps.js that includes a call to goog.add Dependency: goog.addDependency('' /* relPath */, ['goog.events.EventTarget', 'goog.events.EventHandler', 'goog.debug.ErrorHandler'], [] /* requires */); This is a dummy dependency that basically tells the Compiler to assume that these types exist, which will eliminate the warnings. This is better than adding a superfluous goog.require('goog.events.EventTarget') call because that may result in introducing unnecessary extra code in the output. The Compiler will remove calls to goog.add Dependency during compilation, as explained later in this chapter. You might be tempted to declare these types as externs, but that does not satisfy the Compiler because they overlap with actual types. More importantly, it is not semantically correct. Checks Provided by the Compiler | 413 www.it-ebooks.info Access Controls Like how type checking validates the types specified by @param, @return, and @type, the access controls check enforces access to members designated as @private, @protected, or @deprecated. This is a big win over other approaches for enforcing variable privacy, as explained in Appendix A. It turns out that the access controls check requires type checking to be turned on, so enabling the access controls check requires the use of both command-line flags: --jscomp_error=accessControls --jscomp_error=checkTypes Alternatively, the checks for visibility (that ensure that @private and @protected are honored) can be set independently of the checks for the use of deprecated code. Each of these checks has its own warning category, so if you wanted to flag visibility violations as errors but wanted to display only warnings for the use of deprecated code, instead of setting accessControls, you would use: --jscomp_error=visibility --jscomp_warning=deprecated --jscomp_error=checkTypes To be clear, using --jscomp_error=accessControls is equivalent to using: --jscomp_error=visibility --jscomp_error=deprecated Visibility When using @private and @protected, it is important to keep in mind the scope of each access control. For example, a variable marked @private can be accessed from any code in the file where it is defined. This is one of reasons why files should be passed to the Compiler as individual inputs rather than concatenating the sources into a single input file for compilation: doing so would turn make all accesses to @private variables legal from the perspective of the Compiler. Similarly, an @protected member can also be accessed from any code in the file where it is defined, but when the member is defined on the prototype of a class, it may also be accessed from any subclass of that class. The following two files should help make this clear. First, consider the file chocolate.js that defines two classes: example.Chocolate and example.ChocolateFactory: goog.provide('example.Chocolate'); goog.provide('example.ChocolateFactory'); /** * @param {boolean} hasGoldenTicket * @private * @constructor */ example.Chocolate = function(hasGoldenTicket) { this.hasGoldenTicket_ = hasGoldenTicket; }; /** * @type {boolean} * @private */ 414 | Chapter 13: Advanced Compilation www.it-ebooks.info example.Chocolate.prototype.hasGoldenTicket_; /** @return {boolean} */ example.Chocolate.prototype.hasGoldenTicket = function() { return this.hasGoldenTicket_; }; /** * @return {string} * @protected */ example.Chocolate.prototype.getSecretIngredient = function() { return 'anchovies'; }; /** @constructor */ example.ChocolateFactory = function() {}; /** @return {example.Chocolate} */ example.ChocolateFactory.prototype.createChocolate = function() { var hasGoldenTicket = Math.random() < .0001; var chocolate = new example.Chocolate(hasGoldenTicket); this.recordChocolate(chocolate.getSecretIngredient()); return chocolate; }; /** * @param {string} ingredient * @protected */ example.ChocolateFactory.prototype.recordChocolate = function(ingredient) { window.console.log('Created chocolate made with: ' + ingredient); }; Even though the constructor for example.Chocolate is marked @private, an example. Chocolate can still be instantiated by example.ChocolateFactory. Although exam ple.ChocolateFactory is a different class, it is defined in the same file as example.Choc olate, so access is allowed. This is also what makes it possible for example.Chocolate Factory to access getSecretIngredient even though it is not a subclass of exam ple.Chocolate. The second file is named slugworthchocolatefactory.js, and it tries to access information that the visibility modifiers in chocolate.js are trying to protect: goog.provide('example.SlugworthChocolateFactory'); goog.require('example.Chocolate'); goog.require('example.ChocolateFactory'); // Try to create a Chocolate with a golden ticket. var choc1 = new example.Chocolate(true /* hasGoldenTicket */); Checks Provided by the Compiler | 415 www.it-ebooks.info // Try to modify a Chocolate so it has a golden ticket. var factory = new example.ChocolateFactory(); var choc2 = factory.createChocolate(); choc2.hasGoldenTicket_ = true; if (choc2.hasGoldenTicket()) { alert('It worked!'); } // Try to access the secret ingredient from a Chocolate. var choc3 = factory.createChocolate(); alert('This chocolate was made with: ' + choc3.getSecretIngredient()); // Try subclassing ChocolateFactory to extract the secret ingredient. var secretIngredient; /** * @constructor * @extends {example.ChocolateFactory} */ example.SlugworthChocolateFactory = function() {}; /** @inheritDoc */ example.SlugworthChocolateFactory.prototype.recordChocolate = function(ingredient) { secretIngredient = ingredient; }; var slugworthFactory = new example.SlugworthChocolateFactory(); var choc4 = slugworthFactory.createChocolate(); alert('Aha! The secret ingredient is: ' + secretIngredient); Three of the four “attacks” attempted by slugworthchocolatefactory.js are prevented by the Closure Compiler when --jscomp_error=checkTypes and --jscomp_error= visibility are used: slugworthchocolatefactory.js:7: ERROR - Access to private property Chocolate of example not allowed here. var choc1 = new example.Chocolate(true /* hasGoldenTicket */); ^ slugworthchocolatefactory.js:12: ERROR - Overriding private property of example.Chocolate.prototype. choc2.hasGoldenTicket_ = true; ^ slugworthchocolatefactory.js:19: ERROR - Access to protected property getSecretIngredient of example.Chocolate not allowed here. alert('This chocolate was made with: ' + choc3.getSecretIngredient()); ^ 3 error(s), 0 warning(s), 97.0% typed The fourth attempt succeeds because example.ChocolateFactory makes information available to subclasses by making recordChocolate a protected member. Note how even 416 | Chapter 13: Advanced Compilation www.it-ebooks.info though the example.Chocolate constructor is private, it may still have public members, as there is no Compiler error when choc2.hasGoldenTicket() is called. Deprecated members As explained in Chapter 2, the @deprecated tag should be used to identify functions and properties that should no longer be used. By making the use of deprecated code a compilation error, it makes it easy to mark a method as deprecated and then to find all its uses throughout the codebase and remove them. A more gentle approach is to mark the use of deprecated code as a warning so that those who are using the method have some time to implement an alternative solution that does not rely on the deprecated code. As an example: /** * @deprecated Use watchLenoOnTheTonightShow() instead. */ var watchConanOnTheTonightShow = function() {}; var whatAmIGoingToDoTonight = function() { return watchConanOnTheTonightShow(); }; whatAmIGoingToDoTonight(); The error emitted from the Compiler is fairly straightforward: conan.js:8: ERROR - Variable watchConanOnTheTonightShow has been deprecated: Use watchLenoOnTheTonightShow() instead. return watchConanOnTheTonightShow(); ^ 1 error(s), 0 warning(s), 80.0% typed Note how the message that appears after the @deprecated tag appears in the error printed by the Compiler. This helps developers determine how to replace the use of deprecated code. Optimizations Performed by the Compiler Many optimizations that the Compiler performs in Advanced mode were already discussed in the section on what happens during compilation, such as variable renaming, dead code elimination, and property renaming. This section explains some of the additional optimizations that are performed in Advanced mode. Processing Closure Primitives The Compiler has logic that performs special processing on several of the functions defined in base.js of the Closure Library that were introduced in Chapter 3. To ensure Optimizations Performed by the Compiler | 417 www.it-ebooks.info that the Compiler is able to identify these functions, be sure to use them via their original name: goog.provide('example.SlugworthChocolateFactory'); goog.require('example.Chocolate'); goog.require('example.ChocolateFactory'); and not by some alias: var gp = goog.provide, gr = goog.require; gp('example.SlugworthChocolateFactory'); gr('example.Chocolate'); gr('example.ChocolateFactory'); Like the example where goog.string.trim was aliased to gs.trim, doing this type of renaming manually will make the output of the Compiler less efficient. goog.provide The Compiler replaces calls to goog.provide with the code required to create the namespace. This eliminates the uncompressible string that represents the namespace with variables and properties that can be renamed later by the Compiler. For example, goog.provide('foo') becomes: var foo = {}; and goog.provide('foo.bar') becomes: var foo = {}; foo.bar = {}; This new code lends itself to property flattening and variable renaming, in addition to many other optimizations. goog.require Because the Compiler already asserts that every namespace is provided before it is required, there is no need to call goog.require at runtime in the compiled code, so all calls to goog.require are removed by the Compiler. goog.addDependency The Compiler uses the information in goog.addDependency to supplement the dependency graph at compile time. Because all dependencies will be resolved at compile time, there is no need to call goog.addDependency at runtime, so all calls to it are removed. Devirtualizing Prototype Methods Because Closure encourages writing object-oriented JavaScript, Closure code is often full of instance methods and invocations. Such methods have a runtime cost that could 418 | Chapter 13: Advanced Compilation www.it-ebooks.info be eliminated if they were ordinary functions. Fortunately, the Compiler converts such instance methods, when possible. Recall the example.Lamp code introduced earlier in this chapter: goog.provide('example.Lamp'); /** @constructor */ example.Lamp = function() {}; example.Lamp.prototype.isOn_ = false; example.Lamp.prototype.turnOn = function() { this.isOn_ = true; }; example.Lamp.prototype.turnOff = function() { this.isOn_ = false; }; var lamp = new example.Lamp(); lamp.turnOn(); As explained in Chapter 5, the instance methods of example.Lamp could alternatively be defined as static methods: /** @param {example.Lamp} */ example.Lamp.turnOn = function(lamp) { lamp.isOn_ = true; }; /** @param {example.Lamp} */ example.Lamp.turnOff = function(lamp) { lamp.isOn_ = false; }; Though if this change were made, the lamp.turnOn(); statement would have to be updated as well: example.Lamp.turnOn(lamp); Because the Closure Compiler is a whole-program Compiler, when it devirtualizes a method, it can be sure that it has updated all calls to that method, as well. Note that by rewriting the code in this manner, the Compiler is able to make the uncompressed code slightly smaller after performing property flattening and renaming because the this keyword has been removed: // This is the constructor definition. var d = function() {}; // This is the default value of isOn_ being defined on the prototype. d.prototype.a = false; // This is the devirtualized version of turnOn() var g = function(h) { h.a = true; }; // The turnOff() method was removed altogether because it was unused. // This is the instantiation of example.Lamp. var f = new d(); Optimizations Performed by the Compiler | 419 www.it-ebooks.info // This is the call to turnOn(). g(f); There are primarily two cases where an instance method cannot be devirtualized. The first is when a method belongs to a class that has a subclass that overrides the method. As shown in Chapter 5 in the example.sales.putHouseOnTheMarket example, it is not possible to update a call site if the underlying type of the receiver cannot be determined at compile time: /** @param {example.House} house */ example.sales.putHouseOnTheMarket = function(house) { // Because the variable house could be either of type example.House or its // subclass, example.Mansion, which overrides the paint() method, there is // not enough information to determine whether this should become // example.House.paint(house, 'blue') or example.Mansion.paint(house, 'blue'), // so example.House.prototype.paint cannot be devirtualized. house.paint('blue'); }; The other case is when the arguments variable is accessed, because devirtualizing a method changes the number of arguments passed to the function because the instance becomes the new first argument. Consider the following example: goog.provide('example.Stamp'); goog.provide('example.StampCollection'); /** @constructor */ example.StampCollection = function() { /** * @type {Array.} * @private */ this.stamps_ = []; }; /** @typedef {string} */ example.Stamp; /** @param {...example.Stamp} stamps to add */ example.StampCollection.prototype.addToCollection = function(stamps) { for (var i = 0; i < arguments.length; ++i) { this.stamps_.push(arguments[i]); } }; var collection = new example.StampCollection(); collection.addToCollection('upside-down-plane', 'elvis', 'minnesota-statehood'); If addToCollection were devirtualized, the following changes would be made: example.StampCollection.addToCollection = function(collection, stamps) { for (var i = 0; i < arguments.length; ++i) { collection.stamps_.push(arguments[i]); } }; 420 | Chapter 13: Advanced Compilation www.it-ebooks.info var collection = new example.StampCollection(); example.StampCollection.addToCollection(collection, 'upside-down-plane', 'elvis', 'minnesota-statehood'); If this code were run, four arguments would be added to collection.stamps_ instead of three, one of which would be collection itself! For this reason, the Compiler does not attempt to devirtualize any method that references arguments: it cannot safely do so without altering its behavior. Inlining The final set of optimizations to explore in this chapter are those that pertain to inlining. As a good software engineer, you know that instead of hardcoding constants and copying and pasting the same code all over the place, it is better to create an abstraction that encapsulates that information so it can be reused. This results in more maintainable code that is easier to test, but it comes at the cost of runtime performance and code size, both of which are extremely important in JavaScript. Fortunately, the Compiler can take your well-designed code and determine when it is better to replace a variable with its value or a function call with its implementation. This practice is known as inlining. By breaking your abstractions, the Compiler improves runtime performance by eliminating variable lookups and function calls. Though if every function were inlined, your code would become considerably larger, so the Compiler will not inline code if it would result in increasing the size of the output. By understanding the sort of inlining that the Compiler is going to take care of for you, there is no need to prematurely optimize your code by trying to inline functions by hand. Constants and folding The most basic type of inlining performed by the Compiler is inlining constant variables, so code like the following: /** @const */ var secondsPerMinute = 60; /** @const */ var minutesPerHour = 60; /** @const */ var hoursPerDay = 24; /** @const */ var secondsPerDay = hoursPerDay * minutesPerHour * secondsPerMinute; alert(secondsPerDay); will become: /** @const */ var secondsPerDay = 24 * 60 * 60; alert(secondsPerDay); Optimizations Performed by the Compiler | 421 www.it-ebooks.info Because the Compiler also performs constant folding, this code is further reduced to: /** @const */ var secondsPerDay = 86400; alert(secondsPerDay); Now that secondsPerDay has been reduced to a constant, it can also be inlined by the Compiler: alert(86400); String literals are also constants that can be inlined and folded. In particular, the Compiler will also perform folding across indexOf and join methods, in addition to ordinary string concatenation. Given the following input: /** @const */ var nurseryRhyme = ['hickory', 'dickory', 'dock'].join(' '); /** @const */ var beforeTheMouseRunsUpTheClock = nurseryRhyme.indexOf('dock'); The Compiler will first fold the join statement to determine the value of nursery Rhyme at compile time: var nurseryRhyme = 'hickory dickory dock'; Then it will inline nurseryRhyme: var beforeTheMouseRunsUpTheClock = 'hickory dickory dock'.indexOf('dock'); Finally, the indexOf expression will be folded: var beforeTheMouseRunsUpTheClock = 16; Because the Compiler will perform inlining and folding for constants, do not be afraid to extract such values and define them with descriptive variable names because they will impose no extra cost on the size of the compiled output. Though perhaps the most significant reduction in code size comes from inlining and folding boolean constants because they can result in identifying large blocks of dead code that can then be removed at compile time. Consider what happens when defining a workaround for Internet Explorer 6 as follows: if (goog.userAgent.IE && !goog.userAgent.isVersion(7)) { // some large chunk of code that is only needed for IE6. } As mentioned in Chapter 4, if the code is being compiled specifically for Firefox, the Compiler flag --define=goog.userAgent.ASSUME_GECKO=true should be used to eliminate dead code. This works because goog.userAgent.ASSUME_GECKO is used to define the following variable in goog.userAgent: goog.userAgent.BROWSER_KNOWN_ = goog.userAgent.ASSUME_IE || goog.userAgent.ASSUME_GECKO || goog.userAgent.ASSUME_MOBILE_WEBKIT || 422 | Chapter 13: Advanced Compilation www.it-ebooks.info goog.userAgent.ASSUME_WEBKIT || goog.userAgent.ASSUME_OPERA; And then this variable is used to define other constants, such as goog.userAgent.IE and goog.userAgent.GECKO: goog.userAgent.IE = goog.userAgent.BROWSER_KNOWN_ ? goog.userAgent.ASSUME_IE : goog.userAgent.detectedIe_; goog.userAgent.GECKO = goog.userAgent.BROWSER_KNOWN_ ? goog.userAgent.ASSUME_GECKO : goog.userAgent.detectedGecko_; This way, when any of the goog.userAgent.ASSUME constants are set to true using --define at compile time, the value of goog.userAgent.BROWSER_KNOWN_ will also be determined to be true at compile time. This is further propagated so that the previous expressions are reduced to: goog.userAgent.IE = goog.userAgent.ASSUME_IE; goog.userAgent.GECKO = goog.userAgent.ASSUME_GECKO; But because the values on the righthand side are also constants whose values are known at compile time, they will become: goog.userAgent.IE = false; goog.userAgent.GECKO = true; Because goog.userAgent.IE is false, the conditional for the IE6 workaround becomes: if (false && !goog.userAgent.isVersion(7)) { // some large chunk of code that is only needed for IE6. } Because the lefthand side of the && is false, it can be determined at compile time that the righthand side will never be evaluated because the && will short-circuit and evaluate to false before the righthand side is considered. As a result, the conditional is reduced to: if (false) { // some large chunk of code that is only needed for IE6. } Finally, the Compiler can determine the IE6 workaround is unreachable because it lies inside of an if (false) block and therefore will remove it from the compiled output. For this reason, when creating a conditional with multiple clauses, it is best to test the clauses that might be inlined first in order to get better dead code elimination. Variables In addition to inlining variables annotated with the @const keyword, the Compiler will also inline variables that it determines are “safe to inline.” Often, variables that are referenced only once are safe to inline, but that is not always the case. For example, even though x is referenced only once after it is declared in the following example, it would not be safe to replace x with 1: Optimizations Performed by the Compiler | 423 www.it-ebooks.info var x = 1; x++; Variables used in loops or conditionals are also not candidates for inlining: var noInline = function() { for (var i = 0; i < arguments.length; i++) { var x = true; } return !!x; }; If noInline() is called with zero arguments, then it returns false; otherwise, it returns true, so it is not possible to inline a value for x in the return statement at compile time. Fortunately, many other variables can be inlined, such as the ones from the previous section. It turns out that variables such secondsPerMinute or nurseryRhyme do not require the @const tag to enable inlining: they can already be inlined with their initial value because they are never reassigned. However, using the @const tag will guarantee a Compiler error if the variable is reassigned, so it is good practice to use the annotation on top-level constants. Functions The Compiler will also try to inline functions; that is, it will try to replace a function call with its implementation and discard the function declaration. (It will also try to inline a function if its implementation is fewer bytes than the call itself.) This has the benefit of saving bytes and eliminating the overhead of an additional function call. The following is a basic example of inlining at work: goog.require('goog.dom'); /** * @param {number} n1 * @param {number} n2 * @return {number} */ var calculateMean = function(n1, n2) { return (n1 + n2) / 2; }; /** * @param {Array.} arr Must be sorted and have at least one element. * @return {number} */ var calculateMedian = function(arr) { var middle = Math.floor(arr.length / 2); if (arr.length % 2 == 0) { return calculateMean(arr[middle], arr[middle - 1]); } else { return arr[middle]; } }; 424 | Chapter 13: Advanced Compilation www.it-ebooks.info var displayMedian = function() { var values = goog.dom.getElement('values').value.split(',').sort(); var median = calculateMedian(values); goog.dom.getElement('result').innerHTML = median; }; Both calculateMean and calculateMedian are candidates for inlining, so after compilation, both of their declarations are removed and displayMedian becomes: var displayMedian = function() { var a = document.getElementById("values").value.split(",").sort(), b = Math.floor(a.length / 2); a = a.length % 2 == 0 ? (a[b] + a[b - 1]) / 2 : a[b]; document.getElementById("result").innerHTML = a; }; When a function is inlined, its local variables must be given new names to ensure that they do not collide with the names of the existing local variables of the enclosing function. Once inlined, the Compiler is able to make further optimizations to the code, such as combining the declarations of middle and values into a single statement so the var keyword is used only once. It is especially important to be aware of inlining when examining compiled output. When trying to find the compiled code that corresponds to a function declaration in the source code, you may discover that it has “disappeared” even when you are certain its functionality has not been removed. This is likely because it has been inlined by the Compiler and now appears in place of its sole call site. The Compiler is careful not to disrupt the functionality of the original application when inlining code. For example, a recursive function cannot be inlined because it needs to be able to call itself by some name. Also, a function that defines another function is not inlined because that could introduce new variables into the scope of the inner function, which could cause a memory leak. The Compiler will also avoid inlining any function with a reference to this in it. Consider the following instance methods: example.Tire.prototype.inflate = function() { this.fillWithAir(); }; example.Car.prototype.tuneUp = function() { for (var i = 0; i < this.tires_.length; i++) { var tire = this.tires_[i]; tire.inflate(); } }; If the inflate method were inlined, then the tuneUp method would become: example.Car.prototype.tuneUp = function() { for (var i = 0; i < this.tires_.length; i++) { var tire = this.tires_[i]; Optimizations Performed by the Compiler | 425 www.it-ebooks.info } // This following line will not work: tire.this.fillWithAir(); }; The line tire.this.fillWithAir() will throw an error because tire does not have a property named this. Because functions that contain references to this cannot be inlined, the Compiler’s ability to devirtualize prototype methods is even more important in terms of reducing the size of compiled code. By devirtualizing methods and removing references to this, more methods become functions that are candidates for inlining. This is particularly significant with respect to getters, or methods that simply return a value, such as: example.House.prototype.getAddress = function() { return this.address_; }; Due to the object-oriented nature of the Closure Library, getters appear frequently in the code, but calling them is more costly than accessing the property directly because of the function overhead they incur. By using a combination of devirtualization and inlining, code such as this: alert('The address is: ' + house.getAddress()); is rewritten by the Compiler as: alert('The address is: ' + house.address_); However, remember that an overridden method will prevent devirtualization, so the following would prevent the getter from being inlined as shown earlier: /** @inheritDoc */ example.Mansion.prototype.getAddress = function() { return this.address_ + ' (aka "The Villa")'; }; As explained in the section on exporting methods for a public API, it is necessary to expose a public getter method for a property rather than the property itself. Even though consumers of the API will incur the cost of function overhead when accessing your value, the code that lives inside of your library will not. 426 | Chapter 13: Advanced Compilation www.it-ebooks.info CHAPTER 14 Inside the Compiler As demonstrated in the previous chapter, the Closure Compiler is a tremendous tool, but it turns out that it has even more to offer if you are willing to take a peek at the source code. For starters, there are additional optimizations and checks that you can enable if you use the Compiler via its programmatic API, as explained in the section “Hidden Options” on page 436. Then in “Example: Extending CommandLineRunner” on page 450, you will learn how to create your own Compiler executable that will run exactly the compiler passes that you want. Finally, you will explore the Compiler’s internal representation of a JavaScript program in “Example: Visualizing the AST Using DOT” on page 452, before creating your own compiler passes in “Example: Creating a Compiler Check” on page 456 and “Example: Creating a Compiler Optimization” on page 460. But before diving into any of those things, we will take a tour of the codebase of the Closure Compiler. Tour of the Codebase Chapter 12 provided a simple example of using the Closure Compiler via its programmatic API. This section will examine the classes from the com.google.java script.jscomp package used in that example, as well as some others that will be necessary to follow the code samples that appear later in this chapter. Getting and Building the Compiler Although it is possible to explore the code by visiting http://code.google.com/p/closure -compiler/source/browse/, it will be much easier to poke around and experiment with the code if you have a local copy of the repository on your machine. As explained in “Closure Compiler” on page 12, it is possible to check out the code using Subversion and then build the code by running ant jar. Though if you use the Eclipse IDE (http: //www.eclipse.org), it is even easier to work with the code using Eclipse with the Subclipse plugin for accessing Subversion repositories (http://subclipse.tigris.org). The Closure Compiler codebase contains .project and .classpath files that contain 427 www.it-ebooks.info metadata that make it integrate seamlessly with Eclipse as a Java project. As shown in Figure 14-1, when checking out from http://closure-compiler.googlecode.com/svn/ trunk/ using Subclipse, Eclipse automatically checks the “Check out as a project in the workspace” option because it recognizes the .project file. Figure 14-1. Checking out the Closure Compiler using Subclipse. Once you have downloaded the code, switch to the Java perspective in Eclipse and expand the tree in the Package Explorer to view the codebase. As shown in Figure 14-2, there are three directories that contain Java source code: gen, src, and test. The gen directory contains Java code that was generated using Google’s open source protocol buffer compiler, so it is not meant to be edited by hand. (Because the corresponding the .proto files are included in the Compiler’s repository, you can use them 428 | Chapter 14: Inside the Compiler www.it-ebooks.info to regenerate the Java files, if desired.) The src directory contains the source code for the Compiler, so it will be the focus of this section. The test directory contains unit and integration tests for the Compiler’s source code, which also serve as examples of the transformations performed by various compiler passes, so we will be looking at that directory, as well. Figure 14-2. Viewing the Closure Compiler in Package Explorer in Eclipse. Mousing over a type while holding down the Control key (the Command key on a Mac) turns it into a hyperlink. Tour of the Codebase | 429 www.it-ebooks.info Also shown in Figure 14-2 is what happens when the Control key (the Command key on a Mac) is pressed while mousing over a type like CheckLevel in a Java source file in Eclipse: it turns the type into a hyperlink that can be clicked to navigate to the definition of that type in the source code. Alternatively, Control-Shift-T (Option-Shift-T on a Mac) can be used to launch the “Open Type” dialog, where you can type in the name of a Java type (like CheckLevel) and press Enter to open its source file, rather than try to hunt it down in the Package Explorer. These are two of the many features of Eclipse that facilitate browsing an unfamiliar codebase. Eclipse also has built-in support for Ant, so it is not necessary to leave Eclipse to run ant jar from the command line. Right-clicking on the build.xml file in Package Explorer and selecting Run As and then Ant Build... from the context menu will bring up the dialog pictured in Figure 14-3. Figure 14-3. Ant dialog in Eclipse. 430 | Chapter 14: Inside the Compiler www.it-ebooks.info As jar is the default build target declared in the build.xml file, it is automatically checked the first time the Ant dialog is opened. It is possible to select as many targets as you want, and you can specify the order in which to run the targets using the dialog launched by the Order... button. (For example, be sure to run clean before jar when running both of those targets.) To rerun Ant without bringing up the dialog again, you can use the Alt-Shift-X,Q keyboard shortcut (which is Option-Shift-X,Q on a Mac). Upon running ant jar, click the root of the file tree in Package Explorer and press F5 to refresh it. If you expand the build directory, it will now contain a freshly generated version of compiler.jar along with a zip of the externs files named externs.zip. One way to add your own modifications to the Compiler is to edit the source code under the closure-compiler project and run ant jar to build compiler.jar with your modifications, but “Example: Extending CommandLineRunner” on page 450 will show a cleaner way to add your own behavior to the Compiler. Compiler.java Although the Compiler class is the main insertion point for compilation when using the programmatic API, very little of the work of the Closure Compiler is done in Compiler.java itself. The Compiler class is responsible for taking the input, running the appropriate compiler passes based on the compilation options, and then making the output available, which includes the compiled code as well as warnings, errors, or other information collected during compilation. The logic for the compiler passes themselves do not live in Compiler.java, but in other classes in the com.google.java script.jscomp package. The first stage of compilation is parsing the input, which is done by the Compiler’s parse() method. The Compiler parses the externs and input code using Rhino (http:// www.mozilla.org/rhino/), which is an open source JavaScript interpreter written in Java (the Compiler leverages only its parser component). As is standard in most compilers, the parser is responsible for producing an abstract syntax tree (AST) that is used as the model of the code by the remaining phases of the Compiler. (Interestingly, the AST also includes the externs.) If the input is not syntactically correct, then the code will not parse, so no AST will be generated, preventing compilation. If parsing is successful, the root of the AST can be accessed via the getRoot() method of Compiler. Assuming that the code parses successfully, the next step in compilation is running compiler passes. Most compiler passes are classified as either checks or optimizations. Both types of passes operate on the AST: checks identify problems by examining the AST and optimizations improve the input by modifying the AST. Because there is no reason to optimize incorrect code, checks are performed before optimizations. If the checks discover any errors, compilation is halted and no optimizations are performed; but if the checks yield only warnings, then compilation will continue. Tour of the Codebase | 431 www.it-ebooks.info Once the optimizations have finished modifying the AST, compilation is complete. If any warnings or errors were generated in the process of compilation, they may be accessed via the Compiler’s getErrors() and getWarnings() methods, respectively. In order to get the output of the Compiler as JavaScript source code, its toSource() method must be invoked, which uses a CodeBuilder to convert the AST into JavaScript source code. If compilation was unsuccessful due to parse errors and no AST was constructed, then toSource() will return an empty string. CompilerPass.java A compiler pass is represented by a CompilerPass, which is an interface with a single method that gets access to the AST via two Node objects: /** * Process the JS with root node root. * Can modify the contents of each Node tree * @param externs Top of external JS tree * @param root Top of JS tree */ void process(Node externs, Node root); Although it appears that a compiler pass has no way to report an error as a result of inspecting a Node, most implementations of CompilerPass contain a reference to the Compiler responsible for creating the instance of the pass, so errors may be reported though the Compiler’s report(error) method. The error parameter is an instance of JSError, so the Compiler is responsible for determining whether to report a JSError as a warning or an error based on its CompilerOptions. The Compiler class is a subclass of AbstractCompiler, which is an abstract class whose methods are all denoted as abstract. You might wonder why AbstractCompiler is an abstract class rather than an interface; this is because making it an abstract class makes it possible to restrict the visibility of some methods so that they are package-private. This means that there are methods of AbstractCompiler that may be available to a CompilerPass defined inside com.google.javascript.jscomp that are not available to your own CompilerPass defined in a different package. Similarly, an optimization pass that modifies the AST is required to invoke the Compiler’s reportCodeChange() method. Some optimizations are run multiple times because other optimizations that have run since the last iteration may provide new opportunities for improvement. An example of this was seen in Chapter 13, in which alternating between constant folding and inlining resulted in continued improvements to the output code. When rerunning the same set of optimizations results in no new code changes, the code is said to have reached a fixed point. Using reportCodeChange() helps the Compiler identify when a fixed point has been reached so that it can stop applying optimizations. 432 | Chapter 14: Inside the Compiler www.it-ebooks.info JSSourceFile.java Both externs and inputs to the Closure Compiler are expected to be well-formed JavaScript files, so each is represented internally by an object called a JSSourceFile. As demonstrated by the many static factory methods for creating a JSSourceFile (from File(), fromCode(), fromInputStream(), and fromGenerator()), a JSSourceFile need not correspond to a physical file on disk. However, it must have a unique name so that the Compiler can refer to it unambiguously when printing out an error or warning. This is why the example from Chapter 12 must give the JSSourceFile a name, even though there is no file named input.js: JSSourceFile input = JSSourceFile.fromCode("input.js", code); It is helpful to give a JSSourceFile a meaningful name to help track down JavaScript errors when they are reported by the Compiler. CompilerOptions.java The main file of interest for configuring the behavior of the Compiler is Compiler Options.java. As shown in Chapter 12, a call to Compiler.compile() takes a list of externs, a list of inputs, and a CompilerOptions object, so the majority of the configuration for a compilation is encapsulated in that class. Unsurprisingly, the CompilerOptions class contains many fields, each of which corresponds to an option for compilation. Most of these fields are public and can be modified directly, though some are private and must be set via their corresponding setter methods. Although many of the fields in CompilerOptions are booleans for which true is used to enable the option, many of the options that correspond to checks are of type Check Level, which is a simple enum with three values: OFF, WARNING, and ERROR. By default, the value of a CheckLevel option in CompilerOptions is OFF, though flags such as --jscomp_error and --jscomp_warning can be used to alter those values. CompilationLevel.java The three compilation levels supported by the Compiler correspond to the three enum values of CompilationLevel. Specifically, the setOptionsForCompilationLevel (options) method is responsible for mapping a CompilationLevel to the fields to enable on a CompilerOptions object. As the public documentation for each compilation level defines it in a declarative way, examining the source code for CompilationLevel.java is the only way to know exactly which checks and optimizations the Compiler will perform when using a particular compilation level. Tour of the Codebase | 433 www.it-ebooks.info WarningLevel.java The three warning levels supported by the Compiler correspond to the three enum values of WarningLevel. Like setOptionsForCompilationLevel(options) in Compilation Level, there is a setOptionsForWarningLevel(options) method that is responsible for mapping a WarningLevel to the fields to enable on a CompilerOptions object. As the warnings associated with each level are somewhat arbitrary, it is worth examining the source code for WarningLevel.java to see exactly what each warning level will report. PassFactory.java A PassFactory is responsible for creating an instance of a CompilerPass based on the state of an AbstractCompiler, as subclasses of PassFactory must override the following method: /** * Creates a new compiler pass to be run. */ abstract protected CompilerPass createInternal(AbstractCompiler compiler); If a CompilerPass should be run only once ever during compilation, then the isOne TimePass() method of the PassFactory that creates that CompilerPass must return true. For a one-time pass, a single instance of the CompilerPass created by the PassFac tory will be added to the list of compiler passes to run during compilation. However, if isOneTimePass() returns false, then instead of using the factory to create an instance of the CompilerPass, the factory will be added to a collection of factories called a Loop. A Loop is an abstract class that implements CompilerPass. It contains a list of factories that can be used to produce new instances of compiler passes as part of repeated processing to reach a fixed point. A sequence of factories can be added to a Loop, and then the Loop can be added to the list of compiler passes to run during compilation. DefaultPassConfig.java A PassConfig is responsible for mapping a CompilerOptions object to the set of checks and optimizations that the Compiler will run. In practice, DefaultPassConfig is where the majority of this work occurs. To find the compiler pass that corresponds to a field in CompilerOptions, search DefaultPassConfig.java to see where that field is referenced. For example, to find the code responsible for supporting the checkEs5Strict option, a search for options.checkEs5Strict reveals that it is referenced in the getChecks() method: if (options.checkCaja || options.checkEs5Strict) { checks.add(checkStrictMode); } 434 | Chapter 14: Inside the Compiler www.it-ebooks.info Apparently, enabling options.checkEs5Strict results in checkStrictMode being added to the list of compiler passes that are run. The local checkStrictMode field is a Pass Factory that returns an instance of the StrictModeCheck class: /** Checks that the code is ES5 or Caja compliant. */ private final PassFactory checkStrictMode = new PassFactory("checkStrictMode", true) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { return new StrictModeCheck(compiler, !options.checkSymbols, // don't check variables twice !options.checkCaja); // disable eval check if not Caja } }; Therefore, the logic associated with the checkEs5Strict option is in the StrictMode Check class. The class overview in StrictModeCheck.java does a good job of explaining what the compiler pass does, but to find an executable example of StrictModeCheck, take a look at StrictModeCheckTest.java, which is also in the com.google.java script.jscomp package, but in the test folder. By convention, every class that implements CompilerPass should have a corresponding unit test with the same name with Test appended to it. Keeping the test files in the same package means that tests have access to package-private methods, but storing them in a separate folder helps keep the number of files in the src directory down, which is already quite large. CommandLineRunner.java The main class of compiler.jar is CommandLineRunner, so it is responsible for taking command-line flags and using them to create an instance of CompilerOptions that reflects the flag values, as well as the other values for Compiler.compile(). As demonstrated in “Example: Extending CommandLineRunner” on page 450, this class is designed to be subclassed to create your own executable that expands the API for compiler.jar. As the name of the class implies, it is designed to be used when calling the Compiler from the command line only. When using the Compiler programmatically, you can set the fields of CompilerOptions directly—a CompilerOptions should not be created by manipulating the flags of CommandLineRunner. com.google.common.collect The Google Collections Library is an open source Java library that builds upon the Java Collections Framework. It is designed to reduce much of the verbosity involved in using Java generics and make it much easier to create immutable collections. The Collections Library is a subset of the Guava libraries, which is a set of core Java libraries created and used by many projects at Google (http://code.google.com/p/guava-libraries/). As Guava provides utilities for many common IO functions in addition to managing collections, it is used heavily by the Closure Compiler and also appears in code samples Tour of the Codebase | 435 www.it-ebooks.info in this chapter. If you write a lot of Java code, you would be wise to make Guava a part of your Java development. Hidden Options As explained in the previous section, there are many fields in CompilerOptions that are not set by any value of CompilationLevel or a command-line flag. This section examines some options of interest that can be set only programmatically. Checks Although the Compiler already provides a considerable number of checks on its input code, there are some additional checks it can perform that may not apply to all JavaScript projects. checkEs5Strict The most recent draft of the ECMAScript language specification (ES5) introduces a new strict mode that prohibits certain JavaScript constructs, such as the use of the with keyword or the application of delete to a variable or function, as opposed to a property. (The complete list of restrictions can be found in Annex C of the ES5 specification.) The goal of the checkEs5Strict check is to verify whether the input to the Compiler conforms to ES5 strict mode, though it does not yet check everything listed in Annex C. This check can be enabled with a simple boolean: options.checkEs5Strict = true; Although strict mode is designed to help reduce common programming errors in JavaScript, it can also be used to limit the space of JavaScript input to a subset that is easier to analyze, and therefore to secure. For pages like iGoogle that currently sandbox thirdparty code via an iframe, having a “safe subset” of JavaScript that can be statically checked to determine whether it is safe to include verbatim is an attractive option. It is no small coincidence that Douglas Crockford (Yahoo!) and Mark S. Miller (Google) are both ECMAScript committee members who were involved in the design of ES5 strict, while also working on projects that aim to make it possible to safely embed arbitrary JavaScript on a web page (they work on AdSafe and Caja, respectively). If such projects can allow for rich behavior of embedded JavaScript without sacrificing security, then it would be possible to replace the iframes that are currently used to display third-party gadgets or advertisements with the web content of an iframe itself. Eliminating the additional page loads incurred by such iframes would result in faster web pages. Therefore, it is reasonable to expect that sites like iGoogle will require thirdparties to design their web content so that it conforms to strict mode in the future, so those who are in the business of writing embeddable JavaScript would be doing themselves a favor to start reworking their code to meet the requirements of ES5 strict now. 436 | Chapter 14: Inside the Compiler www.it-ebooks.info checkMissingReturn If both checkTypes and checkUnreachableCode are enabled (using Verbose warnings automatically enables both as warnings), then the checkMissingReturn check can be used to flag JavaScript code that appears to be missing a return statement. For example, setting the fields of CompilerOptions as follows: options.checkTypes = true; options.checkUnreachableCode = CheckLevel.WARNING; options.checkMissingReturn = CheckLevel.WARNING; could be used to compile the following code: goog.provide('example'); /** * @param {string} str * @return {boolean} */ example.isPalindrome = function(str) { for (var i = 0, len = Math.floor(str.length / 2); i < len; i++) { if (str.charAt(i) != str.charAt(len - i)) { return false; } } }; which would produce the following warning because a return true; statement is missing from the last line of the function: JSC_MISSING_RETURN_STATEMENT. Missing return statement. Function expected to return boolean. at missingreturn.js line 7 : 31 The Compiler uses the presence of the @return tag in the JSDoc to determine that the function should return a value. It then traces all possible code paths within the function to ensure that a return statement is reached. codingConvention Although it is not a check in its own right, specifying a CodingConvention affects the heuristics that the Compiler uses to make inferences about its input, which affects other checks performed by the Compiler. Some of the methods of the CodingConvention interface include: /** * Checks whether a name should be considered private. */ public boolean isPrivate(String name); /** * This checks whether a given variable name, such as a name in all-caps, * should be treated as if it had the @const annotation. */ public boolean isConstant(String variableName); Hidden Options | 437 www.it-ebooks.info /** * This checks whether a given parameter should be treated as a marker * for a variable argument list function. */ public boolean isVarArgsParameter(Node parameter); The codingConvention field of CompilerOptions refers to a new instance of Default CodingConvention by default, which is one of the three implementations of the Coding Convention interface included in the Compiler’s source code: • DefaultCodingConvention is a trivial implementation of CodingConvention. For example, both isPrivate() and isConstant() always return false, regardless of their input. • ClosureCodingConvention is a subclass of DefaultCodingConvention. It also hardcodes the return value for isPrivate() and isConstant() because Closure code is meant to rely on annotations rather than naming conventions to determine such information. However, for other methods in the CodingConvention interface, such as getExportSymbolFunction(), it returns "goog.exportSymbol", which is specific to the Closure Library. • GoogleCodingConvention is a subclass of ClosureCodingConvention. As you have probably noticed in Closure Library code, private variables always end with an underscore and the marker for a variable argument function is always var_args. This is because historically naming conventions were used rather than annotations in Google JavaScript code, so these conventions had to be hardcoded into the Compiler. This made it convenient to do things such as using a variable whose name is all caps (and more than two letters long) to declare a constant. The only downside to this approach was that developers had to be aware of the heuristics that the Compiler was using. This is in contrast to annotations, which are more transparent about when and where they are used. For legacy reasons, it is likely that Closure Library code will continue to conform to the GoogleCodingConvention as well as the ClosureCodingConvention. Therefore, if you enjoy some of the shortcuts offered by the naming conventions in GoogleCodingConven tion (because typing a trailing underscore is less work than typing @private), then you may wish to override the default value of the codingConvention field in Compiler Options. By default, the Closure Compiler Application redefines codingConvention to be a new instance of ClosureCodingConvention, which is why the code sample in “Programmatic Java API” on page 354 redefines codingConvention so that it is consistent with the Application. reportUnknownTypes When type checking is enabled, the Compiler will print information about what percentage of the variables it was able to provide type information for, using either JSDoc annotations or type inference: 0 error(s), 0 warning(s), 93.8% typed 438 | Chapter 14: Inside the Compiler www.it-ebooks.info If this number is not 100% but you are curious about which code the Compiler believes to be missing type information, configure reportUnknownTypes to be a warning or error to find the offending lines: options.reportUnknownTypes = CheckLevel.ERROR; This option is not meant to be used as part of normal JavaScript development. It is designed to be used as a sanity check for those creating compiler passes to ensure that they are preserving type information. The Closure Library, for example, is not 100% typed, so code that uses it will not be 100% typed, either. Nevertheless, developers are often curious to know where they can add annotations to their code in order to increase their percentage of typed code (from the perspective of the Compiler). For example, compiling the following code with reportUnknownTypes enabled: goog.provide('example.Couch'); /** * @constructor */ example.Couch = function(cushions) { this.cushions_ = cushions; }; /** @return {string} */ example.Couch.prototype.getDescription = function() { return (this.cushions_ > 2) ? 'A comfy couch!' : 'More like a loveseat.'; }; var couch = new example.Couch(3); alert(couch.getDescription()); yields the following errors from the Compiler: Mar 18, 2010 9:09:48 PM com.google.javascript.jscomp.LoggerErrorManager println SEVERE: input.js:6: ERROR - could not determine the type of this expression example.Couch = function(cushions) { this.cushions_ = cushions; }; ^ Mar 18, 2010 9:09:48 PM com.google.javascript.jscomp.LoggerErrorManager println SEVERE: input.js:10: ERROR - could not determine the type of this expression return (this.cushions_ > 2) ? 'A comfy couch!' : 'More like a loveseat.'; ^ Mar 18, 2010 9:09:48 PM com.google.javascript.jscomp.LoggerErrorManager printSummary WARNING: 2 error(s), 0 warning(s), 93.8% typed These error messages help identify the source of the missing type information, which can be fixed by annotating the parameter to the example.Couch constructor: /** * @param {number} cushions * @constructor */ Hidden Options | 439 www.it-ebooks.info This single change eliminates both of the Compiler errors. Renaming Although it may already seem as though the Compiler has renamed every possible variable, it is still possible to squeeze a few more bytes out of it with some additional renaming options. aliasKeywords When aliasKeywords is enabled, all references to true, false, and null are replaced with global variables equal to the same values, saving bytes. It can be enabled with a boolean, though it defaults to false: options.aliasKeywords = true; It turns out that even when aliasKeywords is enabled, it will not perform any aliasing unless a keyword appears at least six times, so for the following input (with constant folding disabled): if (true || true || true) { alert(true || true || true); } if (false || false || false) { alert(false || false || false); } if (null || null || null) { alert(null || null || null); } it becomes the following after the aliasKeywords renaming pass runs: var $$ALIAS_TRUE = true, $$ALIAS_NULL = null, $$ALIAS_FALSE = false; if ($$ALIAS_TRUE || $$ALIAS_TRUE || $$ALIAS_TRUE) { alert($$ALIAS_TRUE || $$ALIAS_TRUE || $$ALIAS_TRUE); } if ($$ALIAS_FALSE || $$ALIAS_FALSE || $$ALIAS_FALSE) { alert($$ALIAS_FALSE || $$ALIAS_FALSE || $$ALIAS_FALSE); } if ($$ALIAS_NULL || $$ALIAS_NULL || $$ALIAS_NULL) { alert($$ALIAS_NULL || $$ALIAS_NULL || $$ALIAS_NULL); } Each of $$ALIAS_TRUE, $$ALIAS_NULL, and $$ALIAS_FALSE will be renamed by later renaming passes in the Compiler. Although this should save bytes, it may not actually do better than gzip in terms of compression, while also introducing more variable lookups at runtime. For these reasons, this pass is disabled by default. 440 | Chapter 14: Inside the Compiler www.it-ebooks.info aliasAllStrings By default, the Compiler does not create an alias for string literals that are used multiple times in compiled code; however, it is possible to instruct the Compiler to alias all string literals with the aliasAllStrings option: options.aliasAllStrings = true; This renaming will likely increase code size because of all the additional variable declarations it will introduce, but it may be necessary when compiling a large JavaScript codebase for an unpatched version of IE6 in order to limit the creation of new string objects. Recall that the Closure Library offers goog.structs.Map as a substitute for JavaScript’s native Object in order to reduce the number of string allocations when iterating over the keys of an object because of the garbage-collection issues in IE6 (http: //pupius.co.uk/blog/2007/03/garbage-collection-in-ie6/). aliasExternals As noted in Chapter 13, the Compiler does not create aliases for externs by default, but you can force it to by enabling the aliasExternals option: options.aliasExternals = true; In Advanced mode, the Compiler does not modify the following input when it is compiled: alert(parseInt('100', 10)); alert(parseInt('200', 10)); alert(parseInt('300', 10)); But if aliasExternals is enabled, it will produce: var a = parseInt; alert(a('100', 10)); alert(a('200', 10)); alert(a('300', 10)); This option is disabled by default because, like aliasKeywords, it may not provide better compression than gzip would already provide. exportTestFunctions The Closure Testing Framework introduced in Chapter 15 provides a number of primitives for running a unit test, such as setUp(), tearDown(), etc. To make it possible to run the compiled version of a test, these functions need to be exported so that they are still accessible by the test runner, so this can be done by enabling the exportTest Functions option: options.exportTestFunctions = true; This will also use goog.exportSymbol() to export every global function that starts with test, as the test runner uses that as a heuristic to identify which functions to run as part of the test. Hidden Options | 441 www.it-ebooks.info Optimizations Many of the optimizations in this section are still considered experimental, which is why they are not supported via the command-line API of the Compiler (compiler options are tested in Google products such as Gmail before making them available via the command-line API). Because of the aggressive changes these optimizations can make to your code, it is particularly important to supplement your application with functional tests that run against the fully optimized version of your JavaScript to ensure that the Compiler has not introduced any breaking behavior by using these options. stripTypePrefixes The stripTypePrefixes option specifies a set of prefixes that the Compiler will use to eliminate expressions in JavaScript. (The prefixes need not correspond to types, so the name of the option is a misnomer.) This is commonly used to remove debugging and assertion code by configuring the option as follows: options.stripTypePrefixes = ImmutableSet.of("goog.debug", "goog.asserts"); Specifying goog.debug will ensure that any reference prefixed with goog.debug will be removed, such as goog.debug.Trace and goog.debug.Logger. For simple calls like the following, the result is fairly straightforward: // Both of these statements throw errors. goog.debug.exposeArray(functionThatReturnsNull()); goog.asserts.assert(goog.string.startsWith('foo', 'bar')); With stripTypePrefixes enabled, both of the previous calls will be removed. In this case, this optimization changes the behavior of the program, because it removes statements that would normally throw errors. In practice, statements such as these should be removed with stripTypePrefixes only if the code is being optimized for production after it has gone through testing that would have made use of this debugging information. What is of greater concern is what happens when a stripped type has a return value, such as the call to goog.debug.Logger.getLogger() in the following example: goog.provide('example.MyClass'); /** @constructor */ example.MyClass = function() { this.id = Math.random(); }; /** @type {goog.debug.Logger} */ example.MyClass.prototype.logger = goog.debug.Logger.getLogger('example.MyClass'); example.MyClass.prototype.getId = function() { this.logger.fine('getId called'); return this.id; }; 442 | Chapter 14: Inside the Compiler www.it-ebooks.info With stripTypePrefixes enabled, the previous code is compiled to the following (inlining and renaming have been disabled for clarity): function example$MyClass() { this.id = Math.random(); } example$MyClass.prototype.logger = null; example$MyClass.prototype.getId = function() { this.logger.fine('getId called'); return this.id; }; Note how the logger property will be null, which means the call to this.logger. fine() in getId() will throw a null pointer error. The Compiler does not produce any warnings or errors when generating this code, so stripTypePrefixes is a dangerous optimization to enable because it may silently introduce null pointer errors resulting from types that are removed. It is often used with stripNameSuffixes, which is discussed in the next section, in order to mitigate this risk. stripNameSuffixes The stripNameSuffixes option specifies a set of suffixes for variable or property names that the Compiler will remove from the source code. This is commonly used in conjunction with stripTypePrefixes to remove loggers as follows: options.stripTypePrefixes = ImmutableSet.of("goog.debug", "goog.asserts"); options.stripNameSuffixes = ImmutableSet.of("logger", "logger_"); Compiling the example from the previous section with both stripTypePrefixes and stripNameSuffixes enabled will produce: function example$MyClass() { this.id = Math.random(); } example$MyClass.prototype.getId = function() { return this.id; }; This removes the declaration of the logger property on example.MyClass.prototype as well as the call to logger.fine() inside getId(). The compiled version of the code executes safely—it just omits the logging logic present in the original code. Although using stripTypePrefixes and stripNameSuffixes in concert reduces the risk of inadvertent null pointer errors, it still requires discipline by the developers to be careful and consistent when naming variables. For example, the compiled version of the following code will behave incorrectly with the stripping options enabled: Hidden Options | 443 www.it-ebooks.info var createPundit = function() { var blogger = { name: 'Joel Spolsky' }; return blogger; }; alert(createPundit().name); As you may have guessed, this compiles to code that has a null pointer error because the local variable blogger has logger as a suffix, so it is stripped: var createPundit = function() { return null; }; alert(createPundit().name); With so many possibilities for subtle errors, why are these stripping optimizations even available? In practice, logging statements often contain string literals that cannot be removed by the Compiler, so eliminating unnecessary logging statements from production code can dramatically reduce code size. Further, goog.debug.Logger has several dependencies (goog.debug, goog.debug.LogRecord, goog.structs, goog.structs.Set, and goog.structs.Map), so eliminating the dependency on goog.debug.Logger may eliminate those dependencies as well, saving even more bytes. This is why introducing goog.net.XhrIo to your application for the first time often results in an unexpectedly large increase in code size: it depends on goog.debug.Logger. Because several projects at Google depend on these optimizations, the only variables in the Closure Library that end in logger or logger_ are indeed assigned to values of type goog.debug.Logger (and vice versa), so it is safe to strip those variables if your application code also follows this convention. setIdGenerators The setIdGenerators option specifies a set of functions that generate unique ids and replaces their calls with the ids themselves at compile time. This is commonly used with goog.events.getUniqueId() as follows: options.setIdGenerators(ImmutableSet.of("goog.events.getUniqueId")); If the input to the compiler were: /** @enum {string} */ example.MyClass.TrafficLightState = { RED: goog.events.getUniqueId('red'), YELLOW: goog.events.getUniqueId('yellow'), GREEN: goog.events.getUniqueId('green') }; then it would be compiled to: /** @enum {string} */ example.MyClass.TrafficLightState = { RED: 'a', YELLOW: 'b', GREEN: 'c' }; 444 | Chapter 14: Inside the Compiler www.it-ebooks.info This saves some bytes by using shorter names for the event types. Note that it is fine to use goog.events.getUniqueId() for user-defined events, but not for browser-defined events, because the browser requires a specific name when adding a listener: /** @enum {string} */ example.IPhone.Gesture.EventType = { GESTURE_START: goog.events.getUniqueId('gesturestart'), GESTURE_MOVE: goog.events.getUniqueId('gesturemove'), GESTURE_END: goog.events.getUniqueId('gestureend') }; // This will not work because the iPhone expects an event type named // 'gesturestart', not 'a' or 'gesturestart_0'. goog.events.listen(el, example.IPhone.Gesture.EventType.GESTURE_START, function(e) { alert('gesture started!'); }); Also, because the function call of an id generator will be replaced with a literal value, it is also unsafe to do the following: var generateNewId = function() { return goog.events.getUniqueId('foo'); }; var id0 = generateNewId(); var id1 = gene