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 = generateNewId(); In uncompiled mode, running this code would result in id0 being 'foo_0' and id1 being 'foo_1', so the two values are unique, as desired. However, if this code were compiled with goog.events.getUniqueId as an id generator, it would become: var generateNewId = function() { return 'a'; }; var id0 = generateNewId(); var id1 = generateNewId(); In compiled mode, both id0 and id1 would be 'a', so the ids would no longer be unique. Generally, an id generator must never be used inside of a function for it to be safe to use with setIdGenerators. disambiguateProperties In Chapter 13, we tried to compile the following code that used example.NumSet: var set = new example.NumSet(); set.addNumber(Math.random()); alert(set.max()); Upon compilation, its max() method was not renamed by the Compiler because it collided with the name of an externed method, Math.max(): var c = new a; c.b(Math.random()); alert(c.max()); // addNumber is renamed to b // max() is not renamed Hidden Options | 445 www.it-ebooks.info Ideally, the Compiler would be able to determine that c is of type example.NumSet, and therefore should be free to rename max(). Fortunately, enabling disambiguateProper ties on CompilerOptions makes this possible by renaming properties to disambiguate between unrelated fields with the same name. (This pass considers only properties that would normally be renamed in Advanced mode.) Consider the following two classes, both of which have a method named getName(): goog.provide('example.Dog'); goog.provide('example.Cat'); /** @constructor */ example.Dog = function(dogName) { this.dogName_ = dogName; }; example.Dog.prototype.getName = function() { return this.dogName_; }; example.Dog.prototype.bark = function() { alert('woof!'); }; /** @constructor */ example.Cat = function(catName) { this.catName_ = catName; }; example.Cat.prototype.getName = function() { return this.catName_; }; var dog = new example.Dog('Sven'); alert(dog.getName()); When disambiguateProperties is enabled, it will use type information (so checkTypes must also be enabled) to rename properties by prepending the namespace of the property, delimited by a combination of underscores and dollar signs: var example = {}; example.Dog = function(dogName) { this.dogName_ = dogName; }; example.Dog.prototype.example_Dog_prototype$getName = function() { return this.dogName_; }; example.Dog.prototype.bark = function() { alert('woof!'); }; example.Cat = function(catName) { this.catName_ = catName; }; example.Cat.prototype.example_Cat_prototype$getName = function() { return this.catName_; }; var dog = new example.Dog('Sven'); alert(dog.example_Dog_prototype$getName()); Now that the names of the getName() methods are unique, the Compiler can use its existing logic for renaming properties. (The bark property is not renamed because renaming occurs only if there are multiple distinct properties with the same name.) In the previous example, disambiguateProperties would rewrite the example.NumSet example as: var set = new example.NumSet(); set.addNumber(Math.random()); alert(set.example_NumSet_prototype$max()); Because example_NumSet_prototype$max is no longer the same as an externed method name (Math.max()), the Compiler is able to rename it as part of its property renaming 446 | Chapter 14: Inside the Compiler www.it-ebooks.info pass. Although this example focuses on eliminating naming collisions with externs, disambiguation can also create new opportunities for other compiler passes as well, such as removing unused methods. Note that there may be cases in which disambiguateProperties cannot determine which object a property refers to, such as the case where a subclass overrides a superclass method. We saw a similar issue in Chapter 13 with devirtualizing prototype methods because of how dynamic dispatch works: /** @param {example.House} house */ example.sales.putHouseOnTheMarket = function(house) { // It is unclear whether this should become either of the following: // house.example_House_prototype$paint('blue'); // house.example_Mansion_prototype$paint('blue'); house.paint('blue'); }; A similar issue arises if there is a union type that includes types with the same property: /** * Makes it rain Cat or Dog, but not both! * @param {example.Dog|example.Cat} pet */ example.makeItRain = function(pet) { // pet.example_Dog_prototype$getName or pet.example_Cat_prototype$getName? if (pet.getName() == 'Sven') { alert('It\'s raining Sven! Hallelujah!'); } }; If either of these cases occurs for a particular property, disambiguateProperties will ensure that the getName() methods of both example.Cat and example.Dog are renamed to the same thing. ambiguateProperties The ambiguateProperties option renames unrelated properties to the same name using type information. This helps reduce code size because more properties can be given short names, and helps improve gzip compression because the same names are more commonly used. When ambiguateProperties is used together with disambiguateProper ties on the previous example, the result is: var example = {}; example.a = function(a) { // dogName_ has been renamed to b this.b = a; }; // getName has been renamed to c example.a.prototype.c = function() { return this.b; }; Hidden Options | 447 www.it-ebooks.info // bark has been renamed to e example.a.prototype.e = function() { alert('woof!'); }; example.d = function(a) { // catName_ has been renamed to b this.b = a; }; // getName has been renamed to c example.d.prototype.c = function() { return this.b; }; var dog = new example.a('Sven'); alert(dog.c()); Even though dogName_ and catName_ are different property names, ambiguateProper ties is able to rename them to the same thing, because it detects that they are defined on different objects and are therefore unrelated. Output In addition to pretty printing and input delimiters, the Compiler offers additional output options that may help with debugging. lineBreak The lineBreak option can be used to use to enable more aggressive line breaking in the output. This can be helpful when compiling in Advanced mode, because it makes it easier to inspect the compiled code and set breakpoints in a JavaScript debugger. This option is set via a boolean: options.lineBreak = true; This option defaults to false. inputDelimiter Chapter 12 introduced the “print input delimiter” option, which is used to insert comments to delimit file boundaries in compiled code. The default format of the input delimiter is not very useful because it contains only the index of the file among the list of Compiler inputs. Fortunately, the format of the delimiter can be configured to include the input name: options.inputDelimiter = "// Input %num%: %name%"; 448 | Chapter 14: Inside the Compiler www.it-ebooks.info Before the Compiler prints the delimiter, it will replace %num% with the input index and %name% with the input name, as determined by the name used when the JSSourceFile was created. colorizeErrorOutput When running the Compiler from the command line and printing to the terminal, it is possible to colorize the error output for the following terminal types: xterm, xtermcolor, xterm-256color, and screen-bce (at least one of these should be available on Mac and Linux platforms). When colorization is enabled, errors will appear in red and warnings will appear in magenta. Because using colorized output will distort what is displayed by the Compiler in the Windows command prompt, it is recommended to initialize the colorizeErrorOutput option as follows: // The colorization logic is not designed to work in the Windows shell. options.colorizeErrorOutput = !System.getProperty("os.name").startsWith("Windows"); The logic that the Compiler uses to display errors can be configured by creating an object that implements the com.google.javascript.jscomp.ErrorManager interface and using it as the parameter to the Compiler’s setErrorManager() method. By default, the Compiler lazily creates an ErrorManager after its CompilerOptions are set, so that it can use the values of the CompilerOptions to determine how to construct the ErrorManager. Therefore, when creating your own ErrorManager, be sure to explicitly set any fields on the ErrorManager (or the AbstractMessageFormatter that it uses) that would normally be read from the CompilerOptions: Compiler compiler = new Compiler(); boolean shouldColorize = !System.getProperty("os.name").startsWith("Windows"); if (shouldColorize) { AbstractMessageFormatter formatter = new LightweightMessageFormatter(compiler); formatter.setColorize(true); compiler.setErrorManager(new CustomErrorManager(formatter, System.err)); } externExports As mentioned in Chapter 13, the Compiler can extract all calls to goog.exportSym bol() and goog.exportProperty() to generate an appropriate externs file. To use this feature, enable the externExports field of CompilerOptions via its corresponding setter method: options.enableExternExports(true); Hidden Options | 449 www.it-ebooks.info It is also a good idea to enable type checking so that the generated externs will include type information: options.checkTypes = true; Once the code is compiled, the externs will be available as a string on Result: Result result = compiler.compile(externs, inputs, options); result.externExport; // This field contains the externs. When used on the linkification example from Chapter 13, the value of result.extern Export is: var mylib = {}; /** * @param {(Node|null)} node * @return {undefined} */ mylib.linkify = function(node) { } Now a client of the linkification library can use this as the content of an externs file when compiling against it. Example: Extending CommandLineRunner A CommandLineRunner is a class that configures and runs the Closure Compiler based on the command-line arguments passed to its main() method. It is designed to be subclassed so that additional compilation options can be enabled automatically, or using user-defined flags. As the file overview indicates, the following is the template to use when creating a subclass of CommandLineRunner: package example; import com.google.javascript.jscomp.CommandLineRunner; import com.google.javascript.jscomp.CompilerOptions; public class MyCommandLineRunner extends CommandLineRunner { protected MyCommandLineRunner(String[] args) { // The superclass is responsible for parsing the command-line arguments. super(args); } @Override protected CompilerOptions createOptions() { // Let the superclass create the CompilerOptions using the values parsed // from the command-line arguments. CompilerOptions options = super.createOptions(); // ENABLE ADDITIONAL OPTIONS HERE. } return options; 450 | Chapter 14: Inside the Compiler www.it-ebooks.info } /** Runs the Compiler */ public static void main(String[] args) { MyCommandLineRunner runner = new MyCommandLineRunner(args); if (runner.shouldRunCompiler()) { runner.run(); } else { System.exit(-1); } } Customizations should be added in createOptions(), so the following can be used to enable disambiguateProperties and ambiguateProperties, which were introduced in the previous section: @Override protected CompilerOptions createOptions() { CompilerOptions options = super.createOptions(); // // // // if The superclass method will enable checkTypes and set the property renaming policy to ALL_UNQUOTED if the appropriate command-line flags were passed, so only use disambiguateProperties and ambiguateProperites when appropriate. (options.checkTypes && options.propertyRenaming == com.google.javascript.jscomp.PropertyRenamingPolicy.ALL_UNQUOTED) { options.disambiguateProperties = true; options.ambiguateProperties = true; } } return options; It is also possible to programmatically set JavaScript variables marked with @define by modifying CompilerOptions. This has the same effect as using the command-line flag -D: // Set goog.DEBUG to false at compile time. options.setDefineToBooleanLiteral("goog.DEBUG", false); // Set goog.LOCALE to 'es' at compile time. options.setDefineToStringLiteral("goog.LOCALE", "es"); // Set example.PORT to 8080 at compile time. options.setDefineToNumberLiteral("example.PORT", 8080); Once you have created your own subclass of CommandLineRunner, you can create your own executable jar using Ant with the following build.xml file: name="build.dir" value="${basedir}/build" /> name="classes.dir" value="${build.dir}/classes" /> name="closure-compiler.jar" value="${basedir}/compiler.jar" /> Example: Extending CommandLineRunner | 451 www.it-ebooks.info Running ant build will compile MyCommandLineRunner and create a new jar file that includes MyCommandLineRunner.class, as well as all of the files in compiler.jar. This new file is named mycompiler.jar to distinguish it from compiler.jar. Because mycompiler .jar recognizes the same flags as compiler.jar, it can be substituted for compiler.jar as the argument to --compiler_jar in calcdeps.py. Failure to override the main() method leads to a confusing error in which calling java -jar mycompiler.jar uses the main() method defined in CommandLineRunner.java, which creates an instance of CommandLineRun ner rather than MyCommandLineRunner. Compilation will still occur, but the customizations added in MyCommandLineRunner.createOptions() will be ignored, making the error hard to track down. Example: Visualizing the AST Using DOT Because compiler passes operate on the AST, it is difficult to create a custom compiler pass without knowing what the AST looks like, so this section will use the Compiler and DOT to create a simple visualization of the AST as an SVG. (Note that the Closure Compiler Application already has a --print_ast flag that will print out a DOT file describing the AST, but it has much more information than we need.) 452 | Chapter 14: Inside the Compiler www.it-ebooks.info What Is DOT? DOT is a plaintext format for describing a graph. In addition to defining the nodes and edges for a graph, it is also possible to add attributes to the elements of a graph in DOT, which will affect its presentation. The following is a simple DOT file named simple.dot that describes a short flowchart: // "digraph" is a keyword indicating this is a directed graph. // "flowchart" is the user-defined name of the graph. digraph flowchart { // Nodes with attributes. start [label="Start" shape=box ]; question [label="Do you like DOT?" shape=diamond]; yes [label="Hooray!" shape=box]; no [label="Give it a chance!" shape=box]; // Directed edges. start -> question; question -> yes [label="YES"]; question -> no [label="NO"]; } // This will force the "question" and "no" nodes to appear on the same level. { rank=same; question no } Although this is a reasonable format for describing a graph, the DOT file on its own does not help visualize it. Fortunately, there is an open source tool named dot that makes it possible to render DOT files in various formats. Pre-built versions of dot are available for all platforms at http://www.graphviz.org/Download.php. As dot is a command-line tool, it can be used to render simple.dot as an SVG as follows: # The -T argument determines the format for the output. # Other valid values for -T include gif, ico, jpg, png, and pdf, among others. dot -T svg simple.dot > simple.svg Figure 14-4 shows simple.svg displayed in a web browser. The beauty of dot is that it handles laying out the nodes and edges in the graph using a sophisticated algorithm to reduce edge crossings and edge length. Edge layout is a complex problem for large, interconnected graphs, which is why dot is so popular for rendering graphs. It is a natural choice for visualizing the AST of the Compiler. Converting the AST to DOT Fortunately, the Compiler already has a class for generating a DOT file for a Node in the AST: com.google.javascript.jscomp.DotFormatter. It has a static method named toDot(Node) that returns the DOT data as a string. Example: Visualizing the AST Using DOT | 453 www.it-ebooks.info Figure 14-4. The generated SVG viewed in Firefox. Recall that the CompilerPass interface gets the root Node of the AST as a parameter, so implementing CompilerPass is a convenient way to hook in DotFormatter: package example; import import import import import import com.google.common.base.Charsets; com.google.common.io.Files; com.google.common.io.InputSupplier; com.google.javascript.jscomp.CompilerPass; com.google.javascript.jscomp.DotFormatter; com.google.javascript.rhino.Node; import java.io.*; public class DotCompilerPass implements CompilerPass { /** The file where the SVG data will be written. */ private final File outputFile; DotCompilerPass(File outputFile) { this.outputFile = outputFile; } @Override public void process(Node externs, Node root) { try { 454 | Chapter 14: Inside the Compiler www.it-ebooks.info // Write the DOT file. File tempFile = File.createTempFile("ast", ".dot"); Files.write(DotFormatter.toDot(root), tempFile, Charsets.US_ASCII); // Convert the DOT file to SVG using the dot executable. Process process = Runtime.getRuntime().exec("dot -T svg " + tempFile.getAbsolutePath()); final InputStream stdout = process.getInputStream(); InputSupplier supplier = new InputSupplier() { public InputStream getInput() { return stdout; } }; } } // Write the output to the outputFile. Files.copy(supplier, outputFile); } catch (IOException e) { e.printStackTrace(); } When DotCompilerPass is constructed, it takes the path where the SVG should be written as its only parameter. Later, when its process() method is called by the Compiler, it writes the result of DotFormatter.toDot() to a temporary file. Then it uses Run time.exec() to call dot to convert the DOT file to an SVG, and writes the output to outputFile. Hooking into MyCommandLineRunner Now that the code for DotCompilerPass is written, it is simply a matter of integrating it into MyCommandLineRunner. Adding a custom pass entails modifying the customPasses field of CompilerOptions, which is a Multimap. A Multimap is a collection defined in Guava that allows multiple values to be added for the same key, so it does not implement java.util.Map. Unfortunately, customPasses is null by default, so it is important to check to see whether it exists before creating a new Multimap: /** * Lazily creates and returns the customPasses Multimap for a CompilerOptions. */ private static Multimap getCustomPasses( CompilerOptions options) { Multimap customPasses = options.customPasses; if (customPasses == null) { customPasses = HashMultimap.create(); options.customPasses = customPasses; } return customPasses; } Example: Visualizing the AST Using DOT | 455 www.it-ebooks.info The key in the customPasses Multimap is a CustomPassExecutionTime, which is an enum with the following values: BEFORE_CHECKS BEFORE_OPTIMIZATIONS BEFORE_OPTIMIZATION_LOOP AFTER_OPTIMIZATION_LOOP Each value indicates a distinct phase of compilation, so associating a CompilerPass with one of these enum values indicates when the Compiler will run the pass during compilation. Because the DotCompilerPass is meant to run on the original AST before it is modified by any compiler optimizations, it should be registered with the BEFORE_CHECKS phase, as follows: // Add these lines to createOptions() in MyCommandLineRunner. Multimap customPasses = getCustomPasses(options); customPasses.put(CustomPassExecutionTime.BEFORE_CHECKS, new DotCompilerPass(new File("ast.svg"))); Now whenever mycompiler.jar runs, it will produce a file named ast.svg that displays the AST of the original input, assuming it was parsed successfully. Example: Creating a Compiler Check In this section, we create a check for the Closure Compiler that will produce an error whenever the == or != operator is used. In JavaScript: The Good Parts (O’Reilly), Douglas Crockford argues that one of the “bad parts” of JavaScript is the == and != operators because they use type coercion when their operands are of different types. This may lead to troublesome scenarios, such as: var a = []; a == !a; // true: the negation of a is equal to a??? a === !a; // false: this is what we expect The first comparison evaluates to true because !a becomes false because [] is a “truthy” value, so its negation is false. When a is compared to a boolean, it is coerced to a string via its toString() method, which happens to produce the empty string, which is a “falsy” value, so both a and !a are considered false, so a == !a evaluates to true. By comparison, a === !a is false, as expected. To eliminate this potential pitfall, it would be great to have a check that flags the use of == or != with a warning or error, though it would be even better if JSDoc could be used to suppress the error when we are sure we want to use == or !=. The following file, equals.js, serves as a representative example of what this check should catch and what it should allow: 456 | Chapter 14: Inside the Compiler www.it-ebooks.info var a = []; // We want this to result in a Compiler error. if (a == !a) { alert('a should not be equal to its negation!'); } var checkIfNullOrUndefined = function(b) { // We want this JSDoc to suppress the Compiler error. if (/** @suppress {double-equals} */ (b == null)) { alert('b is neither null nor undefined!'); } }; Before trying to write the Java code for this compiler pass, use MyCommandLineRunner from the previous section to generate the AST for equals.js with the following command: # This has the side effect of producing ast.svg. java -jar mycompiler.jar --js=equals.js Figure 14-5 shows the resulting representation of the AST. Figure 14-5. The AST for equals.js. Example: Creating a Compiler Check | 457 www.it-ebooks.info The EQ node that appears deeper in the tree is colored green; all of the other nodes are blue. The green color indicates that the EQ node has JSDoc information associated with it, which we will need in order to check for the @suppress annotation. Now that we know what to expect from the structure of the AST, it is fairly easy to create the appropriate compiler pass by leveraging the infrastructure provided by the Closure Compiler: /** * Checks for the presence of the == and != operators, as they are frowned upon * according to Appendix B of JavaScript: The Good Parts. * * @author bolinfest@gmail.com (Michael Bolin) */ public class CheckDoubleEquals implements CompilerPass { // Both of these DiagnosticTypes are disabled by default to demonstrate how // they can be enabled via the command line. /** Error to display when == is used. */ static final DiagnosticType NO_EQ_OPERATOR = DiagnosticType.disabled("JSC_NO_EQ_OPERATOR", "Use the === operator instead of the == operator."); /** Error to display when != is used. */ static final DiagnosticType NO_NE_OPERATOR = DiagnosticType.disabled("JSC_NO_NE_OPERATOR", "Use the !== operator instead of the != operator."); private final AbstractCompiler compiler; CheckDoubleEquals(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, new FindDoubleEquals()); } /** * Traverses the AST looking for uses of == or !=. Upon finding one, it will * report an error unless {@code @suppress {double-equals}} is present. */ private class FindDoubleEquals extends AbstractPostOrderCallback { @Override public void visit(NodeTraversal t, Node n, Node parent) { final int type = n.getType(); if (type == Token.EQ || type == Token.NE) { JSDocInfo info = n.getJSDocInfo(); if (info != null && info.getSuppressions().contains("double-equals")) { return; } 458 | Chapter 14: Inside the Compiler www.it-ebooks.info } } } } DiagnosticType diagnosticType = (type == Token.EQ) ? NO_EQ_OPERATOR : NO_NE_OPERATOR; JSError error = JSError.make(t.getSourceName(), n, diagnosticType); compiler.report(error); This check uses an AbstractPostOrderCallback to perform a NodeTraversal over the AST to look for tokens of type EQ or NE, which correspond to == and !=, respectively. Upon finding one, it checks to see whether the node has been annotated with JSDoc, and if so, checks its @suppress tag to see whether it contains the string "doubleequals". If so, the error is suppressed; otherwise, an appropriate error message is created and reported. To integrate CheckDoubleEquals as part of MyCommandLineRunner, we add it as a custom pass, just like we did for DotCompilerPass in the previous section: customPasses.put(CustomPassExecutionTime.BEFORE_CHECKS, new CheckDoubleEquals(getCompiler())); In this case, CheckDoubleEquals could be specified to run during CustomPassExecution Time.BEFORE_OPTIMIZATIONS instead, but it does not make a difference. To make it possible to enable CheckDoubleEquals from the command line, the Diagnos ticTypes declared in CheckDoubleEquals should be combined into a DiagnosticGroup and registered with the Compiler. This is done by overriding getDiagnosticGroups() in MyCommandLineRunner: @Override protected DiagnosticGroups getDiagnosticGroups() { final DiagnosticGroup group = new DiagnosticGroup( CheckDoubleEquals.NO_EQ_OPERATOR, CheckDoubleEquals.NO_NE_OPERATOR); } return new DiagnosticGroups() { @Override public DiagnosticGroup forName(String name) { return "equalsOperatorCheck".equals(name) ? group : super.forName(name); } }; Because NO_EQ_OPERATOR and NO_NE_OPERATOR were created using DiagnosticType. disabled(), they will be disabled by default, but they can be enabled as warnings or errors using --jscomp_warning or --jscomp_error with equalsOperatorCheck, as specified in forName(): java -jar mycompiler.jar --jscomp_warning=equalsOperatorCheck --js=equals.js Example: Creating a Compiler Check | 459 www.it-ebooks.info Because MyCommandLineRunner extends CommandLineRunner, it already knows how to process the --jscomp_warning and --jscomp_error flags, so no extra logic is needed to handle those arguments. Example: Creating a Compiler Optimization The optimizations available in the Closure Compiler today do an excellent job of exploiting information in the source code in order to minify it, so it is difficult to find new optimizations that result in significant code size reduction. However, there are still many interesting things that can be done with the Compiler in order to rewrite the source code as part of a build process, so this example in this section is an “optimization” in the sense that it rewrites the AST and should run after Compiler checks, but not in the sense that it has a significant impact on code size. Consider the following file, styles.js, that defines a function which takes the URL to a stylesheet and adds a to it in the DOM: goog.provide('example.styles'); /** @param {string} url to a stylesheet to add to the page */ example.styles.addStylesheet = function(url) { var link = document.createElement('link'); link.rel = 'stylesheet'; link.type = 'text/css'; link.href = url; document.getElementsByTagName('head')[0].appendChild(link); }; This function could be used by JavaScript classes that define a UI component to ensure that the appropriate CSS is loaded when the component is used. For example, consider two components that use example.styles.addStylesheet() to load a component, example.component1: goog.provide('example.component1'); goog.require('example.styles'); example.styles.addStylesheet('styles1.css'); example.styles.addStylesheet('styles2.css'); and example.component2: goog.provide('example.component2'); goog.require('example.styles'); example.styles.addStylesheet('styles1.css'); and a file main.js that uses both components: goog.require('example.component1'); goog.require('example.component2'); 460 | Chapter 14: Inside the Compiler www.it-ebooks.info Currently, when main.js is loaded, styles1.css will be loaded twice. This may be acceptable during development, but it will cause the browser to make an extra request when main.js is used in production. Ideally, a single CSS file with the contents of styles1.css and styles2.css would be included in the of a web page that loads main.js, and the calls to example.styles.addStylesheet() would not do anything at all. We will create a compiler pass that creates this stylesheet and removes all of the calls. As we did in the previous section, we will start by using DOT to generate the AST for component2.js to see how a call to example.styles.addStylesheet is represented. The resulting AST is shown in Figure 14-6. Figure 14-6. The AST for component2.js. Each of the three statements in component2.js corresponds to an EXPR_RESULT node in the AST, and the rightmost node corresponds to the example.styles.addStyle sheet('styles1.css') call. As shown in Figure 14-6, the structure that identifies the call to addStylesheet is much more complex than the check for EQ in the previous section. Nevertheless, the code is not too difficult to produce from the AST: public class CollectCssPass implements CompilerPass { private private private private final final final final AbstractCompiler compiler; String directoryPrefix; File cssOutputFile; LinkedHashSet cssFiles = new LinkedHashSet(); Example: Creating a Compiler Optimization | 461 www.it-ebooks.info public CollectCssPass(AbstractCompiler compiler, String directoryPrefix, File cssOutputFile) { this.compiler = compiler; this.directoryPrefix = directoryPrefix; this.cssOutputFile = cssOutputFile; } @Override public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, new AddStylesheetCallback()); if (cssFiles.size() > 0) { try { writeCssFile(); } catch (IOException e) { e.printStackTrace(); } } } private class AddStylesheetCallback extends AbstractPostOrderCallback { } } @Override public void visit(NodeTraversal t, Node n, Node parent) { final int type = n.getType(); if (type == Token.CALL) { String qualifiedName = n.getFirstChild().getQualifiedName(); if ("example.styles.addStylesheet".equals(qualifiedName)) { Node functionArgument = n.getFirstChild().getNext(); if (functionArgument != null && functionArgument.getType() == Token.STRING) { String cssFile = functionArgument.getString(); cssFiles.add(directoryPrefix + cssFile); parent.detachFromParent(); compiler.reportCodeChange(); } } } } private void writeCssFile() throws IOException { StringBuilder builder = new StringBuilder(); for (String cssFile : cssFiles) { Files.copy(new File(cssFile), Charsets.UTF_8, builder); } Files.write(builder, cssOutputFile, Charsets.UTF_8); } 462 | Chapter 14: Inside the Compiler www.it-ebooks.info As was the case for CheckDoubleEquals, most of the interesting work happens in an AbstractPostOrderCallback. Using the Node methods getQualifiedName() and get Type(), the visit() method examines the Node to see whether it matches the structure of an example.styles.addStylesheet() call. Note how the argument to addStyle sheet() is added only if it is a string literal—if it is a variable or the result of an expression, then the call should be left alone. Each argument to addStylesheet() is added to a LinkedHashSet so that there are no duplicates, and the order in which addStylesheet() calls appear in the source code matches the order in which the CSS files will be concatenated. When a value is added to cssFiles, its corresponding call is removed from the AST, removing the call from the output, as desired. This pass can be integrated into MyCommandLineRunner just like the others, though it should be run after the checks are complete: customPasses.put(CustomPassExecutionTime.BEFORE_OPTIMIZATIONS, new CollectCssPass(getCompiler(), "styles/", new File("styles.css"))); Once mycompiler.jar is recreated to include the new pass, then the source code can be compiled as follows: java -jar mycompiler.jar --js=example/styles.js \ --js=example/component1.js \ --js=example/component2.js \ --js=example/main.js \ --formatting=PRETTY_PRINT > main-compiled.js which produces a file named main-compiled.js: var example = {}; example.styles = {}; example.styles.addStylesheet = function(b) { var a = document.createElement("link"); a.rel = "stylesheet"; a.type = "text/css"; a.href = b; document.getElementsByTagName("head")[0].appendChild(a) };example.component1 = {};example.component2 = {}; The compiled JavaScript file no longer has any calls to example.styles.addStyle sheet(), as desired. If this code were compiled in Advanced mode, the declaration of example.styles.addStylesheet would also be removed because it is not used (it is not removed by CollectCssPass in case there are calls to it that do not pass a string literal). Finally, the generated file styles.css is the concatenation of styles1.css and styles2.css, so the generated CSS and JavaScript can be used together in a web page as follows: Example: Creating a Compiler Optimization | 463 www.it-ebooks.info Styles example This significantly reduces the number of HTTP requests made by the browser, improving performance. In this way, the Closure Compiler is more than just a JavaScript compiler: it is also a tool that can be used to add new capabilities to your build process. 464 | Chapter 14: Inside the Compiler www.it-ebooks.info www.it-ebooks.info CHAPTER 15 Testing Framework Testing your code is like flossing: you know that it is important for the hygiene of your codebase, but you neglect to do it anyway. Why is this? Many developers complain that testing is painful: “Tests are too hard to write,” “Writing tests is boring,” or “Testing makes my gums bleed.” Much of this has to do with your coding style and the tools that you use for testing (though if your gums actually start bleeding when you write tests, you should have that checked out). Fortunately, the way that code is written in the Closure Library makes it easy to inspect values from your tests and to mock out functionality to reduce the amount of setup required to create a test. Writing tests will let you respond to your teammates with confidence when they ask about how much “flossing” you have done to ensure that your code will work in production. The Closure Testing Framework that is downloaded with the Closure Library can be used to test any JavaScript code: the code under test need not be written in the style of the Closure Library. The API provided by the Testing Framework is heavily influenced by two other popular testing tools: JsUnit (which itself is modeled after JUnit), and EasyMock. As such, the Closure Testing Framework is primarily designed for creating unit tests, which are tests for very specific units of code. Typically, each JavaScript file in the Closure Library has its own unit test that tests the code in that file and nothing else. This way, if a unit test fails, the failure is localized to the file under test, making it easier to identify and fix. Because most JavaScript files have dependencies on other code, it is common to mock out those dependencies in a unit test so that the test will fail only if there is a problem with the code under test rather than its dependencies. Again, this limits the amount of code that needs to be examined in the event of a failure. There are other types of testing, such as system testing, which are not well supported by the Closure Testing Framework. This chapter will also explore alternative solutions for this type of testing. 465 www.it-ebooks.info Creating Your First Test The section “Closure Testing Framework” on page 19 provides a simple example of how to write a unit test for the Closure Testing Framework. In that example, the test code is spread across two files: the first is an HTML file that loads the test and provides a DOM that the test code can manipulate, and the second is a JavaScript file that contains the actual test code. This deviates from the convention for unit tests in the Closure Library, which is to create a single HTML file to encapsulate a test where the JavaScript test code is included within a Although it is not demonstrated by this example, a test is expected and encouraged to use the DOM of the HTML page that loads it as part of the test when testing JavaScript that operates on the DOM. For example, the host page for the goog.dom unit test, dom_test.html, contains many elements in addition to its Then add the following lines of code to the bottom of the JavaScript file that contains the test: var testCase = new goog.testing.ContinuationTestCase(); testCase.autoDiscoverTests(); G_testRunner.initialize(testCase); Placing this code at the bottom of the file ensures that all global functions declared with var testXXX will be defined before autoDiscoverTests() is called. By initializing G_ testRunner in this manner, the onload handler scheduled by goog.testing.jsunit will recognize that the test runner has already been initialized, so it will not override it with its own goog.testing.TestCase. When the test runner kicks off a goog.testing.ContinuationTestCase, the test case installs the following global functions: waitForTimeout(), waitForEvent(), and waitFor Condition(). These are available to tests so they can schedule additional test functions. When a test is running, each call to one of these wait functions schedules a “step,” so a test will only pass once all of the “steps” registered during the execution of the function succeed. waitForTimeout(continuation, duration) This function schedules continuation to be called after duration milliseconds in the future (if duration is not specified, it defaults to zero). When continuation is called, its code will be executed as part of the test that originally scheduled it. To understand why this is important, consider the following test: // Unexpectedly, this test passes. var testUsingSetTimeout = function() { setTimeout(function() { fail('This does not cause the test to fail because ' + 'this callback is run after the test exits.'); }, 100); }; Even though the test calls fail() directly, the test passes when run on the Closure Testing Framework because the test is run synchronously and the failure does not occur 484 | Chapter 15: Testing Framework www.it-ebooks.info until after the test is complete. By comparison, the following test will fail because it uses waitForTimeout(): // This test fails. var testUsingWaitForTimeout = function() { waitForTimeout(function() { fail('This causes the test to fail. ' + 'The test does not end until this callback completes.'); }, 100); }; Because the test case has registered a “step,” the test is not complete until the callback associated with that step completes. During that callback, fail() is called, so a test failure is recorded, as desired. In some cases, it may be important to verify that an asynchronous callback has been called. This can be checked by performing an assertion in tearDown() instead of in the test itself. The following test will pass only if the function scheduled with waitForTime out() is executed during the test: /** * Should be set to false at the start of a test that wants to verify that some * asynchronous block of code is reached. Upon reaching it, it should be set to * true. */ var reachedFinalContinuation; var setUp = function() { reachedFinalContinuation = true; }; // This test passes. var testContinuationIsCalled = function() { reachedFinalContinuation = false; waitForTimeout(function() { reachedFinalContinuation = true; }, 100); }; var tearDown = function() { assertTrue('The final continuation was not reached', reachedFinalContinuation); }; This technique can be used to verify that it is possible to chain multiple calls together using waitForTimeout(): // This test passes. var testDoubleContinuation = function() { reachedFinalContinuation = false; waitForTimeout(function() { waitForTimeout(function() { reachedFinalContinuation = true; }, 100); }, 100); }; Testing Asynchronous Behavior | 485 www.it-ebooks.info One of the troubling aspects of writing tests for asynchronous code is that if a continuation has assertions that should fail, but an error occurs in such a way that the continuation is never called, then the test may pass when it should not. Using the above technique avoids this error by asserting that the continuation was called. waitForEvent(eventTarget, eventType, continuation) This function schedules continuation to be called after eventTarget dispatches an event whose type is equal to eventType. Unlike a function scheduled with goog.events.lis ten(), the continuation function does not receive the event as an argument, as confirmed by this test: // This test passes. var testCallbackDoesNotGetEvent = function() { var testEventType = 'test-event'; var eventTarget = new goog.events.EventTarget(); var firstListenerCalled = false; goog.events.listenOnce(eventTarget, testEventType, function(e) { firstListenerCalled = true; }); waitForEvent(eventTarget, testEventType, function() { assertEquals('Function should not receive event as an argument', 0, arguments.length); assertTrue('Listeners should be called in order', firstListenerCalled); }); eventTarget.dispatchEvent(testEventType); }; If it takes longer than one second for the event to be dispatched, then waitForEvent() will fail with a timeout error. waitForCondition(condition, continuation, interval, maxTimeout) This function periodically calls the condition function at the specified interval in milliseconds until the condition function returns true. Once this happens, the contin uation function will be called. Alternatively, if condition does not return true within maxTimeout milliseconds, then waitForCondition() will fail with a timeout error. Both interval and maxTimeout are optional arguments that default to 100 and 1000 milliseconds, respectively. Unlike waitForEvent(), it is possible to specify the timeout for waitForCondition(), so for an event that may not be dispatched until more than one second in the future, waitForCondition() is a better choice: // This test passes. var testWaitForCondition = function() { // Here, we use reachedFinalContinuation again to affirm our expectations of // how waitForCondition() should work. reachedFinalContinuation = false; var testEventType = 'test-event'; var eventTarget = new goog.events.EventTarget(); 486 | Chapter 15: Testing Framework www.it-ebooks.info var hasEventFired = false; var typeOfFiredEvent; waitForCondition( // Condition function() { return hasEventFired; }, // Continuation function() { assertEquals(testEventType, typeOfFiredEvent); // Remember, the state of this boolean will be tested in tearDown(). reachedFinalContinuation = true; }, 100, // interval 5000); // maxTimeout goog.events.listenOnce(eventTarget, testEventType, function(e) { typeOfFiredEvent = e.type; hasEventFired = true; }); setTimeout(function() { eventTarget.dispatchEvent(testEventType); }, 4000); }; The ability to customize the length of the timeout may be important when waiting for an XHR with a lot of data to complete, so instead of using waitForEvent() with goog.net.EventType.COMPLETE, use waitForCondition() as demonstrated previously. goog.testing.AsyncTestCase goog.testing.AsyncTestCase is similar to goog.testing.ContinuationTestCase in that it is a subclass of goog.testing.TestCase that has support for testing asynchronous behavior. Whereas goog.testing.ContinuationTestCase provides several functions to aid in testing specific types of asynchronous logic, goog.testing.AsyncTestCase provides a single, uniform mechanism for tracking the flow of an asynchronous test. Choosing between these two APIs is often a matter of taste more than anything else. Like goog.testing.ContinuationTestCase, using goog.testing.AsyncTestCase requires including a snippet of HTML in the test page after the It also requires the following line of JavaScript to appear at the bottom of the JavaScript file that contains the test: var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall(); As its name suggests, this call to createAndInstall() takes care of creating a new instance of goog.testing.AsyncTestCase, invoking its autoDiscoverTests() method, and Testing Asynchronous Behavior | 487 www.it-ebooks.info using it to initialize G_testRunner. Instead of installing global functions, as goog.test ing.ContinuationTestCase does, goog.testing.AsyncTestCase requires test functions to invoke methods on the instance of the test case, to indicate changes in control flow due to asynchronous logic. The two methods of interest are waitForAsync() and continueTesting(). Invoking wait ForAsync() indicates that the test should not end when the current thread of execution is reached. Instead, the test case waits for continueTesting() to be invoked in a new thread of execution, and resumes the test there. Like goog.testing.ContinuationTest Case, these calls can be chained, so if waitForAsync() is invoked after continueTest ing() in the new thread of execution, the test will not end until continueTesting() is called again, as demonstrated in this example: // This test passes. var testDoubleContinuation = function() { // We use this technique again to determine that the innermost callback // scheduled with this test is called before the test ends. reachedFinalContinuation = false; // It is reasonable to invoke waitForAsync() either before or after the // asynchronous logic is scheduled with setTimeout(). asyncTestCase.waitForAsync(); setTimeout(function() { // Because this is a callback to setTimeout(), this code will run in a // different thread of execution than the initial call to // testDoubleContinuation(). // This line is optional because the final call to continueTesting() will // be within one second of the original call to waitForAsync(); however, // the test may be easier to follow when calls to waitForAsync() and // continueTesting() complement one another. asyncTestCase.continueTesting(); setTimeout(function() { // This should be the final thread of execution that this test runs, so // continueTesting() must be called here. asyncTestCase.continueTesting(); // Once again, assertTrue(reachedFinalContinuation) is // called in tearDown() to affirm that this line of code was reached. reachedFinalContinuation = true; }, 300); // Here waitForAsync() is called after setTimeout(). The important thing is // that it is called before the current thread of execution ends. asyncTestCase.waitForAsync(); }, 200); }; 488 | Chapter 15: Testing Framework www.it-ebooks.info Also, additional calls to waitForAsync() before continueTesting() is called will have no effect because the test case is already in waiting mode: // This test will pass even though waitForAsync() is called twice and // continueTesting() is only called once. var testWaitForAsyncCanBeCalledTwice = function() { asyncTestCase.waitForAsync('wait for it...'); // This is redundant because asyncTestCase is already in waiting mode. asyncTestCase.waitForAsync('wait for it......'); asyncTestCase.continueTesting(); }; The default timeout for goog.testing.AsyncTestCase is one second. That is, after wait ForAsync() is invoked, continueTesting() must be called within one second; otherwise, the test will fail with a timeout failure. This value can be changed by modifying the value of asyncTestCase.stepTimeout, though this changes the length of the timeout globally. This is in contrast to waitForCondition() in goog.testing.ContinuationTest Case, which allows the length of the timeout to be set on a per-call basis. Running a Single Test As you have probably noticed, many of the files in the Closure Library have their own unit tests, though the HTML files to load those tests are slightly simpler than email validator_test.html. Specifically, they do not specify their deps file via a This is because the deps.js file for the Closure Library has already been generated and is available at the root of the goog directory in the Library. Recall from Chapter 3 that base.js tries to load a deps.js file in the same directory as itself. This makes it possible to run any of the Closure Library tests simply by loading them in a browser—no other configuration is required. (However, if you add new dependencies to the Closure Library, then you will have to regenerate its deps.js file using calcdeps.py as you do for your own dependencies.) Running a Single Test | 489 www.it-ebooks.info Running Multiple Tests Although it is convenient to be able to run a test simply by opening a web page, what happens when you have dozens of test cases that you want to run at once? At the time of this writing, the Closure Library has 324 test cases—that’s a lot of tabs to open in your browser! Fortunately, Closure provides the goog.testing.MultiTestRunner class to help with this exact problem. In the root of the Closure Library repository, there is a file named all_tests.html that leverages goog.testing.MultiTestRunner to run all of the unit tests for the Closure Library and to display the results in a simple interface as shown in Figure 15-3. Figure 15-3. goog.testing.MultiTestRunner in action. 490 | Chapter 15: Testing Framework www.it-ebooks.info When all_tests.html is loaded, there is an empty, white progress bar with a button labeled Start below it that must be clicked to kick off the test runner. As the tests are running, the progress bar grows to the right: green sections of the progress bar indicate passing tests, whereas red sections indicate failing tests. Mousing over a section of the bar displays a tooltip with the name of the test that corresponds to the red or green section. Failing tests are listed in the “Report” tab with a link to run the failing test individually. Unfortunately, as shown in Figure 15-3, not all the tests for the Closure Library pass at the time of this writing. When using code from the Closure Library that has a failing test, you may want to investigate the failure so that you are aware of any potential problems you may encounter. Reusing this interface for your own tests is fairly simple. The all_tests.html file determines what tests to run based on the _allTests array (defined in alltests.js in the same directory). By redefining _allTests with paths to your own test files, all_tests.html can be used to run your tests. Note that this may also require generating an appropriate deps.js file and loading it in all_tests.html to ensure that calls to goog.require() work correctly while running the test. As you may have guessed from the formatting of the alltests.js file, the value of _allTests is generated by running a script: it is simply a list of the files that end in _test.html. Unfortunately, the script used to generate _allTests is not available in the repository, though the following Python script should do the job: #!/usr/bin/python """Given a list of directories, prints out a JavaScript array of paths to files that end in "_test.html". The output will look something like: var _allTests = [ "example/emailvalidator_test.html", "example/factorial_test.html"]; """ import os.path import sys def add_test_files(test_files, dirname, names): """File names that end in "_test.html" are added to test_files.""" for name in names: path = os.path.join(dirname, name) if os.path.isfile(path) and name.endswith('_test.html'): pathJsArg = ('"' + path + '"').replace('\\', '/') test_files.append(pathJsArg) def find_test_files(directory): """Returns the list of files in directory that end in "_test.html".""" if not os.path.exists(directory) or not os.path.isdir(directory): raise Exception('Not a directory: ' + directory) Running Multiple Tests | 491 www.it-ebooks.info test_files = [] os.path.walk(directory, add_test_files, test_files) return test_files def usage(): """Displays a message to the user when invalid input is supplied.""" print 'Specify a list of directories that contain _test.html files' def main(): """Prints the list of JS test files to standard out.""" if len(sys.argv) < 2: usage() sys.exit(-1) else: test_files = [] for directory in sys.argv[1:]: test_files.extend(find_test_files(directory)) print 'var _allTests = [' print ', '.join(test_files) + '];' if __name__ == '__main__': main() Following the naming convention of having test files end in _test.html makes this script easy to write. Automating Tests Some developers make the audacious claim that testing is worthless. If the tests that you write never get run, then there is some validity to that complaint, though the correct response is to start automating your tests so that they are run regularly rather than to stop writing them. Fortunately, there is a free, open source tool named Selenium that makes it possible to automate running unit tests from the Closure Testing Framework on multiple browsers. Selenium is a large project with multiple subprojects, but you need to download just “Selenium Remote Control (RC)” from the Downloads page (http://seleniumhq.org/ download/) in order to build an automated test runner. Once you have unzipped the contents of the downloaded file, find the file named selenium-server.jar and run it as described at http://seleniumhq.org/docs/05_selenium _rc.html#running-selenium-server: java -jar selenium-server.jar Selenium offers client libraries for various programming languages, but the example in this section will use Java. As explained at http://seleniumhq.org/docs/05_selenium_rc .html#using-the-java-client-driver, you will need to write some Java code that compiles against selenium-java-client-driver.jar, which should be included in the zip file that you have already downloaded. The following class extends SeleneseTestCase, which 492 | Chapter 15: Testing Framework www.it-ebooks.info further extends junit.Framework.TestCase, so it can be run just like any other JUnit test in Java: package example; import org.junit.Before; import org.junit.Test; import com.thoughtworks.selenium.SeleneseTestCase; public class GoogStringTest extends SeleneseTestCase { private final String DIRECTORY_PREFIX = "file:///C:/Users/mbolin/workspace/closure-library/"; @Override @Before public void setUp() throws Exception { final String url = DIRECTORY_PREFIX; final String browserString = "*firefox"; // This initializes the protected "selenium" field with the base URL for the // tests and the browser to use when running the tests. setUp(url, browserString); } @Test public void testGoogString() throws Exception { // Opens the URL in Firefox. selenium.open(DIRECTORY_PREFIX + "closure/goog/string/string_test.html"); // Because the test runs automatically when the HTML file is loaded, poll // for up to 5 seconds to see whether the test is complete. selenium.waitForCondition( "window.G_testRunner && window.G_testRunner.isFinished()", "5000"); } } // Invoke this snippet of JavaScript in the browser to query whether the // test succeeded or failed. String success = selenium.getEval("window.G_testRunner.isSuccess()"); boolean isSuccess = "true".equals(success); if (!isSuccess) { // If the test failed, report the reason why. String report = selenium.getEval("window.G_testRunner.getReport()"); fail(report); } This test will load string_test.html in Firefox and will repeatedly evaluate the JavaScript expression window.G_testRunner && window.G_testRunner.isFinished() until either it evaluates to true or a period of five seconds elapses. (For whatever reason, even though the second parameter to waitForCondition() is a number, the Selenium Java API declares it to be a String.) Assuming waitForCondition() succeeds, the next step is to evaluate window.G_testRunner.isSuccess() to determine the outcome of the test. Automating Tests | 493 www.it-ebooks.info Because the Selenium method getEval() returns a String, it is compared to "true" to determine whether the Closure test passed. If the test failed, it is helpful to get the report from G_testRunner so it can be included with the failure message for the JUnit test. At the time of this writing, there is a bug in Selenium that prevents it from working on Firefox 3.6. According to the bug report, the fix has been made in the source tree, but there has not been a new release of Selenium RC that contains the fix yet: http://code.google.com/p/sele nium/issues/detail?id=363. Because Selenium takes care of automating the browser, it is now possible to run your Closure tests programmatically from Java, or with any of the other client libraries supported by Selenium. It should now be straightforward to use an existing tool such as cron or the Windows Task Scheduler to run your tests regularly and email you the results. Assuming that such a program is configured to run against the latest version of your code, you will now be notified when a change to your codebase causes your tests to fail. Because such a system helps you identify regressions quickly, writing tests becomes much more valuable. The example in this section is only a small sample of what is possible with Selenium. However, as mentioned earlier in this chapter, the Selenium and WebDriver projects are in the process of merging as part of the Selenium 2.0 release, which means that Selenium is somewhat of a moving target. Fortunately, the project has comprehensive documentation available at http://seleniumhq.org/docs/, so if you want to monitor the progress of the project and learn about more of its advanced features, you should be able to find the information you need on the website. System Testing A system test is a high-level test that is conducted on a complete, integrated system. For example, a system test of the Google search engine might automate a web browser to navigate to http://www.google.com, type a query in the search box, click on the “Google Search” button, and then verify that the appropriate search results are displayed. This is important to ensure that the system works end-to-end. In end-to-end testing of a web application, a system test should imitate the behavior of a real user as closely as possible. In doing so, a passing system test gives you a high level of confidence in the correctness of your application. Another benefit is that failures of a system test should be easy to reproduce manually (when they imitate the behavior of a real user), which is often helpful in debugging. Unfortunately, the Closure Testing Framework is not well suited to running system tests. As explained in “Testing Input Events” on page 483, the Framework does not use native input events, making it impossible to faithfully simulate real user input. Further, each unit test runs on a single page, so if the web application is designed so 494 | Chapter 15: Testing Framework www.it-ebooks.info that the user navigates between web pages, performing a page transition will tear down the test. Ideally, a system test for a web application will know nothing about the underlying structure of the DOM. For example, if the name of the form field is changed from q to query, the system test should not have to be updated in order for it to pass: a change that is transparent to a user should also be transparent to a system test. Some tools, such as Chickenfoot, http://groups.csail.mit.edu/uid/chickenfoot/, facilitate writing tests in this style. The following is a snippet of Chickenscratch (which is the superset of JavaScript that Chickenfoot accepts as input) that tests doing a Google search for MIT: go('http://www.google.com/'); enter('MIT'); // This enters the string 'MIT' in the search box click('Google Search'); var anchor = find('first MIT link').element; if (anchor.href != 'http://web.mit.edu/') { throw new Error('First MIT link should point to "http://web.mit.edu/" ' + ', but pointed to ' + anchor.href); } Note that the code does not contain any references to DOM nodes, and there is no explicit synchronization to ensure that the search results page has been loaded before the assertion is performed. Because Chickenfoot has heuristics for determining the meaning of click('Google Search'), the Google Search button need not be mapped to a DOM node explicitly as part of the test. This is a desirable property of a system test: by testing user behavior rather than system internals, it is possible to perform large refactorings of the underlying codebase while using system tests (that should not have to be modified as part of the refactoring) to ensure that the changes will not affect the user experience. This is not the case with unit tests or integration tests, which will likely have to be created, edited, and deleted as part of the refactoring. Unfortunately, Chickenfoot is a Firefox extension, so it is not possible to use it for testing on web browsers other than Firefox. Because browser behavior often varies, it is important to run system tests on all of the browsers that your users may use to access your web application. Here, Selenium is a better alternative to Chickenfoot, though its API encourages using hardcoded node ids and CSS classes to identify elements on the page, which makes system tests brittle to changes in the underlying page structure. However, creating the proper abstraction in your JavaScript code will make your system tests more flexible. Instead of writing the following Java code in Selenium to automate a click on the Google Search button: selenium.click("name=btnG"); include the following JavaScript code in your web application: System Testing | 495 www.it-ebooks.info goog.provide('example.TestDriver'); /** @define {boolean} true to export test methods */ example.TestDriver.EXPORT_TEST_METHODS = false; example.TestDriver.getSearchButton = function() { return document.forms['f']['btnG']; }; if (example.TestDriver.EXPORT_TEST_METHODS) { goog.exportSymbol('example.TestDriver.getSearchButton', example.TestDriver.getSearchButton); }; Then update your Selenium code accordingly (this example uses a WebDriver API, which will be part of Selenium 2.0): WebElement searchButton = remoteWebDriver.executeScript( 'example.TestDriver.getSearchButton()'); searchButton.click(); Now the Selenium test no longer depends on the underlying structure of the page, but it does depend on the correctness of the example.TestDriver API. However, by limiting the dependency to example.TestDriver, it is still possible to refactor the application code without updating the system tests. Note how the call to goog.exportSymbol() is wrapped in a boolean that can be set at compile time. This ensures that the end user will not download the additional code required to support the test driver unless the production version of the JavaScript is compiled with --define=example.TestDriver.EXPORT_TEST_METHODS=true. Thus, a special version of the web application will have to be built using this Compiler flag in order to use this technique. 496 | Chapter 15: Testing Framework www.it-ebooks.info CHAPTER 16 Debugging Compiled JavaScript Despite all of the static checks that the Closure Compiler can perform, it is still possible for your code to contain bugs. If you have leveraged the Compiler to minify your JavaScript, then it will be hard to step through your code with a debugger, as most debuggers let you step through your code only one line at a time. (Note that the IE8 debugger does not have this limitation.) In hand-written code, in which one line of code generally contains at most one statement, this works fine, but because minified code has many statements per line, using a traditional debugger will not provide the granularity that you need. Compiling code in Advanced mode compounds this problem because renaming and inlining optimizations obfuscate and reorder code in such a way that even if you identify the line of code at which the error occurs in the compiled code, it may be extremely difficult to map back to the line in the original source code. To that end, this chapter discusses several techniques that can be used to help in debugging compiled JavaScript, many of which are specifically tailored for debugging code that was compiled using Advanced mode. Verify That the Error Occurs in Uncompiled Mode As explained in Chapter 1, one of the design principles of Closure Library code is that it should work in both compiled and uncompiled modes. This means that if you encounter a bug in your compiled code, your first step should be to test whether you can reproduce the bug in the uncompiled version. If such a bug is reproducible, then it will likely be easier to debug using the uncompiled code, as all of your traditional techniques for JavaScript debugging will still work. Also, double-check to make sure that your code compiles without any warnings, and use the Verbose warning level to maximize the number of checks performed by the Compiler. You may be lucky enough to discover that the Compiler has already found your bug for you! 497 www.it-ebooks.info When the bug occurs only in compiled mode and there are no warnings from the Compiler, then debugging may be more challenging. The first step is to review the restrictions on input that the Compiler imposes, as explained at http://code.google.com/ closure/compiler/docs/limitations.html. If the code works before compilation but not after, then there is a good chance that your code violates one of those restrictions. Though as an aside, code that works in compiled mode may not work in uncompiled mode, such as the following: goog.provide('example.TrafficLight'); goog.provide('example.TrafficLight.State'); /** @enum {string} */ example.TrafficLight.State = { RED: 'red', YELLOW: 'yellow', GREEN: 'green' }; /** @constructor */ example.TrafficLight = function() { this.state = example.TrafficLight.State.RED; }; alert((new example.TrafficLight()).state); In uncompiled mode, the definition of example.TrafficLight will clobber the definition of example.TrafficLight.State, so when this code is run, it produces the following error: TypeError: example.TrafficLight.State is undefined However, when this code is compiled in Advanced mode, the enum values may be inlined such that the declaration of the enum is removed, so the compiled version of this code is: alert((new function(){this.a="red"}).a); The compiled code alerts the string "red", as expected. This is why having some understanding of the transformations that the Compiler will apply to your code is so important in knowing where to look for bugs. Format Compiled Code for Debugging If you are unable to find the source of your bug via manual inspection, then you will need to run your code as part of the debugging process. As explained in Chapter 10, using logging statements can be useful for reporting the state of your application while it is running, which may be all you need to determine the source of a bug. The logging approach has the benefit that it will work on any web browser, and does not require installing any special tools. 498 | Chapter 16: Debugging Compiled JavaScript www.it-ebooks.info However, to capture the information that you need in order to solve your problem in a logging statement, you must start out with some suspicions about where the bug exists, so that you know where to add your logging code. If these suspicions turn out to be incorrect, then you may end up investing a lot of time writing useless logging code that takes you down the wrong path. Further, adding such code does alter your program, so you may introduce new problems in the process of adding and removing logging code as you debug. An effective alternative to logging is to use a JavaScript debugger. Although each browser has its own debugger application, each lets you suspend the execution of your program when it reaches a particular line (known as a breakpoint), and investigating the program state, either by inspecting the object graph directly or by creating a watch expression, which is a snippet of JavaScript code that is repeatedly reevaluated as you step through the program. The latest versions of Chrome, Safari, and Internet Explorer each have their own JavaScript debugger as part of a suite of developer tools bundled with the browser. By comparison, Firefox does not include its own debugger application, but many web developers use the free Firebug extension, which includes a JavaScript debugger, among other web tools. (Firebug also serves as the platform for the Closure Inspector, as explained later in this chapter.) By inspecting code through a debugger, it eliminates the step of instrumenting your source code as part of the debugging process. However, as mentioned at the beginning of this chapter, most debuggers expect developers to step through code one line at a time, as one line of code traditionally contains one statement at most. Obviously minified JavaScript code does not meet that expectation, so it is helpful to enable the “pretty print” option introduced in Chapter 12 so that it is easier to set breakpoints in a debugger. However, pretty printing is only part of the solution. Even if you are able to find the line of code that is responsible for a bug, there is still the matter of mapping that line of compiled code back to the source code in order to determine where to apply your bug fix. One Compiler option that may help with this issue is “Print Input Delimiter,” which identifies input file boundaries in compiled code via a JavaScript comment. Recall from Chapter 14 that the inputDelimiter option can configured to display the name of the source file in the comment: // Input: hello.js a('example.sayHello', function(a) { /* ... */ }); This way, when a problematic line is found in compiled code, its corresponding source file can be determined by finding the first input delimiter that precedes it. The one flaw in this approach is that if code is inlined from one file into another, the input delimiter will be misleading because it will identify the final destination of the inlined code, not its source. One trick for tracking how code moves as result of compilation is to include a debug ger statement inside code that is being inlined. The debugger statement is a special Format Compiled Code for Debugging | 499 www.it-ebooks.info command in JavaScript that does not take any arguments, like break and continue. When JavaScript code is executed in an environment where a debugger is enabled, the debugger will treat the statement as a breakpoint. If debugging is not enabled, then the debugger statement is ignored at runtime. Because the Closure Compiler does not know whether its output will be run under a debugger, it treats a debugger statement like any other function that has a side effect, which means it will not be removed during compilation (though an unused function that contains a debugger statement may still be removed). Consider the following example: var getHref = function() { // Firebug will treat this as a breakpoint when it encounters this line. debugger; return window.location.href; }; Before window.location.href is returned, the debugger statement must be executed, so to determine where getHref() is defined in compiled code, simply run the compiled code that executes getHref() under a debugger. Upon reaching the debugger statement, it will trigger a breakpoint in the debugger, which should make it straightforward to identify where the compiled equivalent of getHref() is declared in the compiled code. Compile with --debug=true Another option that may help with debugging code that is renamed by the Compiler in Advanced mode is --debug=true. Using --debug=true inserts names for anonymous functions and uses pseudo-renaming rather than aggressive renaming, so it is easier to map the compiled name to the original name. For example, consider the following input: example.Simple.prototype.setValue = function(value) { this.value_ = value; }; example.Simple.prototype.getValue = function() { return this.value_; }; When compiled in Advanced mode with --debug=true, it becomes: $example$Simple$$.prototype.$setValue$ = function $$example$Simple$$$$$setValue$$($value$$11$$) { this.$value_$ = $value$$11$$ }; $example$Simple$$.prototype.$getValue$ = function $$example$Simple$$$$$getValue$$() { return this.$value_$ }; Note how the original function assigned to example.Simple.prototype.setValue has been given a name: $$example$Simple$$$$$setValue$$ (the same is true of getValue()). For an anonymous function that would normally appear as (?)() in the stack trace of 500 | Chapter 16: Debugging Compiled JavaScript www.it-ebooks.info a debugger, it will now display the name inserted by the Compiler, making the stack trace easier to understand. The --debug=true option also renames variables and properties with additional dollar signs so that it is easier to map them back to their original names. Using this verbose renaming should tease out any bugs that result from hardcoding the name of a property or variable that is renamed by the Compiler. The --debug=true flag is frequently used in conjunction with --formatting=PRETTY_ PRINT and --formatting=PRINT_INPUT_DELIMITER. Use the Closure Inspector As explained earlier in this chapter, determining the mapping between source code and compiled code can be a bit of work, but fortunately the Closure Compiler can generate this mapping for you. As shown in “Closure Inspector” on page 21, passing the --create_source_map flag to the Closure Compiler will produce a new file that contains a source map that defines the mapping between lines of source code and lines of compiled code. Rather than trying to make sense of this file on your own, you should use the Closure Inspector to interpret the source map for you while debugging. The Closure Inspector is an extension to Firebug that uses the source map to show the source code that corresponds to compiled code while using the JavaScript debugger. After setting the location of the source map as shown in Figure 1-6, it is possible to select any of the JavaScript code under the Script tab in Firebug and select “Show Original Source” from the context menu to find out where the selected code is defined in the source code. Figure 16-1 shows this feature in action when used with compiled code that has been pretty-printed. Figure 16-1. Selecting “Show Original Source” in the Closure Inspector. Use the Closure Inspector | 501 www.it-ebooks.info When “Show Original Source” is selected, it tries to show the original source in a text editor, though if no text editing application is set via the EDITOR environment variable, the Closure Inspector pops open an alert box like the one shown in Figure 16-2. Figure 16-2. Alert box from “Show Original Source.” Because it leverages the source map directly, using the Closure Inspector is often the most effective way to map between source code and compiled code. The only downside to using the Inspector is that its use is currently limited to Firefox. At the time of this writing, the Closure Inspector works with any version of Firefox 3 that supports Firebug 1.5. Because the Closure Inspector depends on both Firebug and Firefox, an update to either of those tools may not be compatible with the Closure Inspector. In general, the Closure Inspector should be updated to support the latest versions of both Firefox and Firebug, though it may not always happen as quickly as you would like. When such a situation arises, you may have to keep an older version of Firefox or Firebug around in order to use the Inspector, or you may have to make the appropriate changes to the Inspector yourself. Because the Closure Inspector has a mapping for every line of compiled code, it can also format stack traces of compiled code as though they came from the original source. Such formatted traces replace the stack trace normally provided by Firebug, as shown in Figure 16-3. The Closure Inspector is an excellent tool, though it is really the source map that makes it possible to work with compiled code. For example, a web application that encounters a JavaScript error could send its stack trace to the server, where it could be deobfuscated and then logged using a source map. Another possibility is using a source map to dynamically generate JavaScript code on the server that uses the same abbreviated property names as the objects already loaded by compiled code on the client, as suggested at http://groups.google.com/group/closure-compiler-discuss/browse_thread/ thread/96deaad90882e27d#msg_0eb00b51ee7d48b7. The format of the source map file itself is described in the Closure Compiler source code in SourceMap.java. 502 | Chapter 16: Debugging Compiled JavaScript www.it-ebooks.info Figure 16-3. Deobfuscated stack trace in the Closure Inspector. Use the Closure Inspector | 503 www.it-ebooks.info www.it-ebooks.info APPENDIX A Inheritance Patterns in JavaScript This originally appeared as an article on my website on November 6, 2009. The Closure Library makes use of the pseudoclassical inheritance pattern, which is particularly compelling when used with the Closure Compiler. Those of you who have read JavaScript: The Good Parts by Douglas Crockford (O’Reilly) may use the functional pattern for inheritance that he espouses. Crockford appears to object to the pseudoclassical pattern because “There is no privacy; all properties are public. There is no access to super methods . . . . Even worse, there is a serious hazard with the use of constructor functions. If you forget to use the new prefix when calling a constructor function, then this will not be bound to a new object . . . . There is no compile warning, and there is no runtime warning” (p. 49). This appendix discusses the advantages of the pseudoclassical pattern over the functional pattern. I argue that the pattern used by the Closure Library paired with the Closure Compiler removes existing hazards, and I also examine the hazards introduced by the functional pattern (as defined in JavaScript: The Good Parts). First let me demonstrate what I mean by the functional pattern. Example of the Functional Pattern The following example demonstrates the style of the functional pattern for inheritance as explained in Douglas Crockford’s JavaScript: The Good Parts. It contains the definition for a phone type as well as a subtype smartPhone. var phone = function(spec) { var that = {}; that.getPhoneNumber = function() { return spec.phoneNumber; }; 505 www.it-ebooks.info that.getDescription = function() { return "This is a phone that can make calls."; }; return that; }; var smartPhone = function(spec) { var that = phone(spec); spec.signature = spec.signature || "sent from " + that.getPhoneNumber(); that.sendEmail = function(emailAddress, message) { // Assume sendMessage() is globally available. sendMessage(emailAddress, message + "\n" + spec.signature); }; var super_getDescription = that.superior("getDescription"); that.getDescription = function() { return super_getDescription() + " It can also send email messages."; }; return that; }; Instances of each of these types could be created and used as follows: var myPhone = phone({"phoneNumber": "8675309"}); var mySmartPhone = smartPhone({"phoneNumber": "5555555", "signature": "Adios"}); mySmartPhone.sendEmail("noone@example.com", "I can send email from my phone!"); Example of the Pseudoclassical Pattern Here is the same logic as the previous example, except that it is written using Closure’s style and coding conventions: goog.provide('Phone'); goog.provide('SmartPhone'); /** * @param {string} phoneNumber * @constructor */ Phone = function(phoneNumber) { /** * @type {string} * @private */ this.phoneNumber_ = phoneNumber; }; 506 | Appendix A: Inheritance Patterns in JavaScript www.it-ebooks.info /** @return {string} */ Phone.prototype.getPhoneNumber = function() { return this.phoneNumber_; }; /** @return {string} */ Phone.prototype.getDescription = function() { return 'This is a phone that can make calls'; }; /** * @param {string} phoneNumber * @param {string=} signature * @constructor * @extends {Phone} */ SmartPhone = function(phoneNumber, signature) { Phone.call(this, phoneNumber); /** * @type {string} * @private */ this.signature_ = signature || 'sent from ' + this.getPhoneNumber(); }; goog.inherits(SmartPhone, Phone); /** * @param {string} emailAddress * @param {string} message */ SmartPhone.prototype.sendEmail = function(emailAddress, message) { // Assume sendMessage() is globally available. sendMessage(emailAddress, message + '\n' + this.signature_); }; /** @inheritDoc */ SmartPhone.prototype.getDescription = function() { return SmartPhone.superClass_.getDescription.call(this) + ' It can also send email messages.'; }; Similarly, here is an example of how these types could be used: goog.require('Phone'); goog.require('SmartPhone'); var phone = new Phone('8675309'); var smartPhone = new SmartPhone('5555555', 'Adios'}; smartPhone.sendEmail('noone@example.com', 'I can send email from my phone!'); Example of the Pseudoclassical Pattern | 507 www.it-ebooks.info Drawbacks to the Functional Pattern This section enumerates problems with using the functional pattern, particularly when writing JavaScript that will be compiled with the Closure Compiler. Instances of Types Take Up More Memory Every time phone() is called, two new functions are created (one per method of the type). Each time, the functions are basically the same, but they are bound to different values. These functions are not cheap because each is a closure that maintains a reference for every named variable in the enclosing function in which the closure was defined. This may inadvertently prevent objects from being garbage collected, causing a memory leak. The Closure Library defines goog.bind() and goog.partial() in base.js to make it easier to create closures that maintain only the references they need, making it possible for other references to be removed when the enclosing function exits. This is not a concern when Phone() is called because of how it takes advantage of prototype-based inheritance. Each method is defined once on Phone.prototype and is therefore available to every instance of Phone. This limits the number of function objects that are created and does not run the risk of leaking memory. Methods Cannot Be Inlined When possible, the Compiler will inline methods, such as simple getters. This can reduce code size as well as improve runtime performance. Because the methods in the functional pattern are often bound to variables that cannot be referenced externally, there is no way for the Compiler (as it exists today) to rewrite method calls in such a way that eliminates the method dispatch. By comparison, the following code snippet: // phone1 and phone2 are of type Phone var caller = phone1.getPhoneNumber(); var receiver = phone2.getPhoneNumber(); operator.createConnection(caller, receiver); could be rewritten to the following by the Compiler: operator.createConnection(caller.phoneNumber_, receiver.phoneNumber_); Superclass Methods Cannot Be Renamed (Or Will Be Renamed Incorrectly) When the Closure Compiler is cranked up to 11, one of the heuristics it uses for renaming is that any property that is not accessed via a quoted string is allowed to be renamed, and the Compiler will do its best to rename it. All quoted strings will be left alone. You are not required to use the Compiler with this aggressive setting, but the potential reduction in code size is too big to ignore. 508 | Appendix A: Inheritance Patterns in JavaScript www.it-ebooks.info From the phone example, getDescription() is used both as a property defined on that and as a string literal passed to that.superior(). If aggressive renaming were turned on in the Compiler, getDescription() would have to be used as a quoted string throughout the codebase so that it did not get renamed. (It could also be exported for each instance of phone using goog.exportProperty().) Remembering to refer to it via a string literal throughout the codebase is a bear and precludes the benefits of aggressive renaming. (To be fair, some of the constructs in the candidate spec for ECMAScript 5, such as Object.defineProperty(), have similar issues. The solution will likely be to add logic to the Compiler to treat Object.defineProperty() in a special way. The same could be done for superior() if one were so motivated.) Types Cannot Be Tested Using instanceof Because there is no function to use as the constructor, there is no appropriate argument to use with the right side of the instanceof operator to test whether an object is a phone or a smartPhone. Because it is common for a function to accept multiple types in JavaScript, it is important to have some way to discern the type of the argument that was passed in. An alternative would be to test for properties that the desired type in question may have, such as: if (arg.sendEmail) { // implies arg is a mobilePhone, do mobilePhone things } There are two problems with this solution. The first is that checking for the send Email property is only a heuristic—it does not guarantee that arg is a mobilePhone. Perhaps there is also a type called desktopComputer that has a sendEmail() method that would also satisfy the previous test. The second problem is that this code is not selfdocumenting. If the conditional checked if (arg instanceof MobilePhone), it would be much clearer what was being tested. Encourages Adding Properties to Function.prototype and Object.prototype In Chapter 4 of JavaScript: The Good Parts, Crockford introduces Function.proto type.method() and uses it in Chapter 5 via Object.method() to add a property named superior to Object.prototype to aid in creating superclass methods. Adding properties to fundamental prototypes makes it harder for your code to play nicely with other JavaScript libraries on the page if both libraries modify prototypes in conflicting ways. The Google Maps team learned this the hard way when they decided to add convenience methods to Array.prototype, such as insertAt() (incidentally, this is exactly what Crockford does in Chapter 6 of his book). It turned out that many web developers were using for (var i in array) to iterate over the elements of an array (as opposed to using for (var i = 0; i < array.length; i++) as they should have been). The for (var i Drawbacks to the Functional Pattern | 509 www.it-ebooks.info in array) syntax includes properties added to Array.prototype as values of i, so bringing in the Google Maps library would break the code on those web pages. Rather than trying to change web developers’ habits, the Maps team changed their library. It is for reasons such as these that array.js in Closure is a collection of array utility functions that take an array as their first argument rather than a collection of modifications to Array.prototype. For those of you who do not own Crockford’s book, here is the code in question. Try running the following and see what happens: // From Chapter 4. Function.prototype.method = function(name, func) { this.prototype[name] = func; return this; }; // From Chapter 5. Object.method('superior', function(name) { var that = this, method = that[name]; return function() { return method.apply(that, arguments); }; }); // Create a new object literal. var obj = { "one": 1, "two": 2 }; // Enumerate the properties of obj. for (var property in obj) alert(property); Instead of enumerating two properties in obj, three are listed: one, two, and superior. Now every object literal in your program will also have a property named superior. Though it may be handy for objects that represent classes, it is inappropriate for ordinary record types. Makes It Impossible to Update All Instances of a Type In the Closure model, it would be possible to add a field or method to all instances of a type at any time in the program by adding a property to the function constructor’s prototype. This is simply not possible in the functional model. Although this may seem like a minor point, it can be extremely useful when developing or debugging a system to redefine a method on the fly (by using a read-eval-print-loop, or REPL, such as the Firebug console). This makes it possible to put probes into a running system rather than having to refresh the entire web page to load changes. 510 | Appendix A: Inheritance Patterns in JavaScript www.it-ebooks.info Naming Newly Created Objects Is Awkward This is more of an issue with Crockford’s recommended style than the functional pattern itself, but the “capitalize constructor functions” convention makes it fairly intuitive to name a newly created object. From the pseudoclassical example, we have: var phone = new Phone('8675309'); where the name of the variable matches that of the constructor, but has a lowercase letter. If the same thing were done in the functional case, it could cause an error: function callJenny() { var phone = phone('8675309'); makeCall(phone.getPhoneNumber()); } Here the local variable phone shadows the function phone(), so callJenny() will throw an error when called because phone is bound to undefined when it is applied to '8675309'. Because of this, it is common to add an arbitrary prefix, such as my, to the variable name for newly created objects when using the functional pattern. Results in an Extra Level of Indentation Again, this may be more of a personal preference, but requiring the entire class to be defined within a function means that everything ends up being indented one level deeper than it would be when using the Closure paradigm. I concede that this is how things are done in Java, and C# even suggests adding an extra level of depth for good measure with its namespaces, so maybe there’s some prize for hitting the Tab key a lot that no one told me about. Regardless, if you have ever worked someplace (like Google) where 80-character line limits are enforced, it is nice to avoid line wrapping where you can. Potential Objections to the Pseudoclassical Pattern When using the pseudoclassical pattern in Closure, the traditional objections to the pseudoclassical pattern are no longer a concern. Won’t Horrible Things Happen if I Forget the New Operator? If a function with the @constructor annotation is called without the new operator, the Closure Compiler will emit an error when the --jscomp_error=checkTypes option is used. (Likewise, if the new operator is used with a function that does not have the annotation, it will also throw an error.) Crockford’s objection that there is no compiletime warning no longer holds! (I find this odd because Crockford could have created his own annotation with an identical check in JSLint.) Potential Objections to the Pseudoclassical Pattern | 511 www.it-ebooks.info Didn’t Crockford Also Say I Wouldn’t Have Access to Super Methods? Yes, but as the previous example demonstrates, a superclass’s methods are accessible via the superClass_ property added to a constructor function. This property is added as a side effect of calling goog.inherits(SmartPhone, Phone). Unlike the functional example with super_getDescription, superclass accessors do not need to be explicitly created in Closure. Won’t All of the Object’s Properties Be Public? It depends on what you mean by “public.” All fields and methods of an object marked with the @private annotation will be private in the sense that the Compiler can be configured to reject the input if a private property is being accessed outside of its class. As long as all of the JavaScript in your page is compiled together, the Compiler should preclude any code paths that would expose private data. Won’t Declaring SomeClass.prototype for Each Method and Field of SomeClass Waste Bytes? Not really. The Compiler will create a temporary variable for SomeClass.prototype and reuse it. As the Compiler works today, it is admittedly most compact to write things in the following style: SomeClass.prototype = { getFoo: function() { return this.foo_; }, setFoo: function(foo) { this.foo_ = foo; }, toString: function() { return ''; } }; However, this style has some drawbacks. Doing things this way introduces an extra level of indenting and makes reordering methods more tedious, because extra care is required to ensure that the last property declared does not have a trailing comma and that all of the other properties do. I think that it is reasonable to expect the Compiler to produce output more similar to the previous output in the future, but the recommended input will continue to match the style exemplified in the pseudoclassical example. 512 | Appendix A: Inheritance Patterns in JavaScript www.it-ebooks.info I Don’t Need Static Checks—My Tests Will Catch All of My Errors! Let’s be honest—do you write tests? And if you are writing tests, how much confidence are they giving you about your code’s correctness? As I’ve written previously, existing tools for testing web applications make it difficult to write thorough tests. So even if you have taken the time to write tests, it is unlikely that they exercise all of your code. (Lack of good tools for measuring code coverage by JavaScript tests contributes to the problem.) By comparison, the Closure Compiler examines your entire program and provides many compile-time checks that can help you find errors before running your code. If Douglas Crockford claims that JSLint will hurt your feelings, then the Closure Compiler will put you into therapy. Potential Objections to the Pseudoclassical Pattern | 513 www.it-ebooks.info www.it-ebooks.info APPENDIX B Frequently Misunderstood JavaScript Concepts This book is not designed to teach you JavaScript, but it does recognize that you are likely to have taught yourself JavaScript and that there are some key concepts that you may have missed along the way. This section is particularly important if your primary language is Java, as the syntactic similarities between Java and JavaScript belie the differences in their respective designs. JavaScript Objects Are Associative Arrays Whose Keys Are Always Strings Every object in JavaScript is an associative array whose keys are strings. This is an important difference from other programming languages, such as Java, where a type such as java.util.Map is an associative array whose keys can be of any type. When an object other than a string is used as a key in JavaScript, no error occurs: JavaScript silently converts it to a string and uses that value as the key instead. This can have surprising results: var foo = new Object(); var bar = new Object(); var map = new Object(); map[foo] = "foo"; map[bar] = "bar"; // Alerts "bar", not "foo". alert(map[foo]); 515 www.it-ebooks.info In the previous example, map does not map foo to "foo" and bar to "bar". When foo and bar are used as keys for map, they are converted into strings using their respective toString() methods. This results in mapping the toString() of foo to "foo" and the toString() of bar to "bar". Because both foo.toString() and bar.toString() are "[object Object]", the previous code is equivalent to: var map = new Object(); map["[object Object]"] = "foo"; map["[object Object]"] = "bar"; alert(map["[object Object]"]); Therefore, map[bar] = "bar" replaces the mapping of map[foo] = "foo" on the previous line. There Are Several Ways to Look Up a Value in an Object There are several ways to look up a value in an object, so if you learned JavaScript by copying and pasting code from other websites, it may not be clear that the following code snippets are equivalent: // (1) Look up value by name: map.meaning_of_life; // (2) Look up value by passing the key as a string: map["meaning_of_life"]; // (3) Look up value by passing an object whose toString() method returns a // string equivalent to the key: var machine = new Object(); machine.toString = function() { return "meaning_of_life"; }; map[machine]; Note that the first approach, “Look up value by name,” can be used only when the name is a valid JavaScript identifier. Consider the example from the previous section where the key was "[object Object]": alert(map.[object Object]); // throws a syntax error This may lead you to believe that it is safer to always look up a value by passing a key as a string rather than by name. In Closure, this turns out not to be the case because of how variable renaming works in the Compiler. This is explained in more detail in Chapter 13. Single-Quoted Strings and Double-Quoted Strings Are Equivalent In some programming languages, such as Perl and PHP, double-quoted strings and single-quoted strings are interpreted differently. In JavaScript, both types of strings are 516 | Appendix B: Frequently Misunderstood JavaScript Concepts www.it-ebooks.info interpreted in the same way; however, the convention in the Closure Library is to use single-quoted strings. (By comparison, Closure Templates mandates the use of singlequoted strings.) The consistent use of quotes makes it easier to perform searches over the codebase, but they make no difference to the JavaScript interpreter or the Closure Compiler. The one caveat is that the JSON specification requires that strings be double-quoted, so serialized data that is passed to a strict JSON parser (rather than the JavaScript eval() function) must use double-quoted strings. There Are Several Ways to Define an Object Literal In JavaScript, the following statements are equivalent methods for creating a new, empty object: // This syntax is equivalent to the syntax used in Java (and other C-style // languages) for creating a new object. var obj1 = new Object(); // Parentheses are technically optional if no arguments are passed to a // function used with the 'new' operator, though this is generally avoided. var obj2 = new Object; // This syntax is the most succinct and is used exclusively in Closure. var obj3 = {}; The third syntax is called an “object literal” because the properties of the object can be declared when the object is created: // obj4 is a new object with three properties. var obj4 = { 'one': 'uno', 'two': 'dos', 'three': 'tres' }; // Alternatively, each property could be added in its own statement: var obj5 = {}; obj5['one'] = 'uno'; obj5['two'] = 'dos'; obj5['three'] = 'tres'; // Or some combination could be used: var obj6 = { 'two': 'dos' }; obj6['one'] = 'uno'; obj6['three'] = 'tres'; Note that when using the object literal syntax, each property is followed by a comma, except for the last one. Care must be taken to keep track of commas, as this is often forgotten when later editing code to add a new property: There Are Several Ways to Define an Object Literal | 517 www.it-ebooks.info // Suppose the declaration of obj4 were changed to include a fourth property. var obj4 = { 'one': 'uno', 'two': 'dos', 'three': 'tres' // Programmer forgot to add a comma to this line... 'four': 'cuatro' // ...when this line was added. }; This code will result in an error from the JavaScript interpreter because it cannot parse the object literal due to the missing comma. Currently, all browsers other than Internet Explorer allow a trailing comma in object literals to eliminate this issue (support for the trailing comma is mandated in ES5, so it should appear in IE soon): var obj4 = { 'one': 'uno', 'two': 'dos', 'three': 'tres', // This extra comma is allowed on Firefox, Chrome, and Safari. }; Unfortunately, the trailing comma produces a syntax error in Internet Explorer, so the Closure Compiler will issue an error when it encounters the trailing comma. Because of the popularity of JSON, it is frequent to see the keys of object literals as double-quoted strings. The quotes are required in order to be valid JSON, but they are not required in order to be valid JavaScript. Keys in object literals can be expressed in any of the following three ways: var obj7 = { one: 'uno', 'two': 'dos', "three": 'tres' }; // No quotes at all // Single-quoted string // Double-quoted string Using no quotes at all may seem odd at first, particularly if there is a variable in scope with the same name. Try to predict what happens in the following case: var one = 'ONE'; var obj8 = { one: one }; The previous code creates a new object, obj8, with one property whose name is one and whose value is 'ONE'. When one is used on the left of the colon, it is simply a name, but when it is used on the right of the colon, it is a variable. This would perhaps be more obvious if obj8 were defined in the following way: var obj8 = {}; obj8.one = one; Here it is clearer that obj8.one identifies the property on obj8 named one, which is distinct from the variable one to the right of the equals sign. 518 | Appendix B: Frequently Misunderstood JavaScript Concepts www.it-ebooks.info The only time that quotes must be used with a key in an object literal is when the key is a JavaScript keyword (note this is no longer a restriction in ES5): var countryCodeMap = { fr: 'France', in: 'India', // Throws a syntax error because 'in' is a JavaScript keyword ru: 'Russia' }; Despite this edge case, keys in object literals are rarely quoted in Closure. This has to do with variable renaming, which is explained in more detail in Chapter 13 on the Compiler. As a rule of thumb, only quote keys that would sacrifice the correctness of the code if they were renamed. For example, if the code were: var translations = { one: 'uno', two: 'dos', three: 'tres' }; var englishToSpanish = function(englishWord) { return translations[englishWord]; }; englishToSpanish('two'); // should return 'dos' Then the Compiler might rewrite this code as: var a = { a: 'uno', b: 'dos', c: 'tres' }; var d = function(e) { return a[e]; }; d('two'); // should return 'dos' but now returns undefined In this case, the behavior of the compiled code is different from that of the original code, which is a problem. This is because the keys of translations do not represent properties that can be renamed, but strings whose values are significant. Because the Compiler cannot reduce string literals, defining translations as follows would result in the compiled code having the correct behavior: var translations = { 'one': 'uno', 'two': 'dos', 'three': 'tres' }; There Are Several Ways to Define an Object Literal | 519 www.it-ebooks.info The prototype Property Is Not the Prototype You Are Looking For For all the praise for its support of prototype-based programming, manipulating an object’s prototype is not straightforward in JavaScript. Recall that every object in JavaScript has a link to another object called its prototype. Cycles are not allowed in a chain of prototype links, so a collection of JavaScript objects and prototype relationships can be represented as a rooted tree where nodes are objects and edges are prototype relationships. Many modern browsers (though not all) expose an object’s prototype via its __proto__ property. (This causes a great deal of confusion because an object’s __proto__ and prototype properties rarely refer to the same object.) The root of such a tree will be the object referenced by Object.prototype in JavaScript. Consider the following JavaScript code: // Rectangle is an ordinary function. var Rectangle = function() {}; // Every function has a property named 'prototype' whose value is an object // with a property named 'constructor' that points back to the original // function. It is possible to add more properties to this object. Rectangle.prototype.width = 3; Rectangle.prototype.height = 4; // Creates an instance of a Rectangle, which is an object whose // __proto__ property points to Rectangle.prototype. This is discussed // in more detail in Chapter 5 on Classes and Inheritance. var rect = new Rectangle(); Figure B-1 contains the corresponding object model. In the diagram, each box represents a JavaScript object and each circle represents a JavaScript primitive. Recall that JavaScript objects are associative arrays whose keys are always strings, so each arrow exiting a box represents a property of that object, the target being the property’s value. For simplicity, the closed, shaded arrows represent a __proto__ property, while closed, white arrows represent a prototype property. Open arrows have their own label indicating the name of the property. The prototype chain for an object can be found by following the __proto__ arrows until the root object is reached. Note that even though Object.prototype is the root of the graph when only __proto__ edges are considered, Object.prototype also has its own values, such as the built-in function mapped to hasOwnProperty. When resolving the value associated with a key on a JavaScript object, each object in the prototype chain is examined until one is found with a property whose name matches the specified key. If no such property exists, the value returned is undefined. This is effectively equivalent to the following: 520 | Appendix B: Frequently Misunderstood JavaScript Concepts www.it-ebooks.info Figure B-1. Graph of prototype links between objects. var lookupProperty = function(obj, key) { while (obj) { if (obj.hasOwnProperty(key)) { return obj[key]; } obj = obj.__proto__; } return undefined; }; For example, to evaluate the expression rect.width, the first step is to check whether a property named width is defined on rect. From the diagram, it is clear that rect has no properties of its own because it has no outbound arrows besides __proto__. The next step is to follow the __proto__ property to Rectangle.prototype, which does have an outbound width arrow. Following that arrow leads to the primitive value 3, which is what rect.width evaluates to. The prototype Property Is Not the Prototype You Are Looking For | 521 www.it-ebooks.info Because the prototype chain always leads to Object.prototype, any value that is declared as a property on Object.prototype will be available to all objects, by default. For example, every object has a property named hasOwnProperty that points to a native function. That is, unless hasOwnProperty is reassigned to some other value on an object, or some object in its prototype chain. For example, if Rectangle.prototype.hasOwnProp erty were assigned to alert, then rect.hasOwnProperty would refer to alert because Rectangle.prototype appears earlier in rect’s prototype chain than Object.prototype. Although this makes it possible to grant additional functionality to all objects by modifying Object.prototype, this practice is discouraged and error-prone, as explained in Chapter 4. Understanding the prototype chain is also important when considering the effect of removing properties from an object. JavaScript provides the delete keyword for removing a property from an object: using delete can affect only the object itself, but not any of the objects in its prototype chain. This may sometimes yield surprising results: rect.width = 13; alert(rect.width); // alerts 13 delete rect.width; alert(rect.width); // alerts 3 even though delete was used delete rect.width; alert(rect.width); // still alerts 3 When rect.width = 13 is evaluated, it creates a new binding on rect with the key width and the value 13. When alert(rect.width) is called on the following line, rect now has its own property named width, so it displays its associated value, 13. When delete rect.width is called, the width property defined on rect is removed, but the width property on Rectangle.prototype still exists. This is why the second call to alert yields 3 rather than undefined. To remove the width property from every instance of Rectangle, delete must be applied to Rectangle.prototype: delete Rectangle.prototype.width; alert(rect.width); // now this alerts undefined It is possible to modify rect so that it behaves as if it did not have a width property without modifying Rectangle.prototype, by setting rect.width to undefined. It can be determined whether the property was overridden or deleted by using the built-in hasOwnProperty method: var obj = {}; rect.width = undefined; // Now both rect.width and obj.width evaluate to undefined even though obj // never had a width property defined on it or on any object in its prototype // chain. rect.hasOwnProperty('width'); // evaluates to true obj.hasOwnProperty('width'); // evaluates to false Note that the results would be different if Rectangle were implemented as follows: 522 | Appendix B: Frequently Misunderstood JavaScript Concepts www.it-ebooks.info var Rectangle2 = function() { // This adds bindings to each new instance of Rectangle2 rather than adding // them once to Rectangle2.prototype. this.width = 3; this.height = 4; }; var rect1 = new Rectangle(); var rect2 = new Rectangle2(); rect1.hasOwnProperty('width'); // evaluates to false rect2.hasOwnProperty('width'); // evaluates to true delete rect1.width; delete rect2.width; rect1.width; // evaluates to 3 rect2.width; // evaluates to undefined Finally, note that the __proto__ properties in the diagram are not set explicitly in the sample code. These relationships are managed behind the scenes by the JavaScript runtime. The Syntax for Defining a Function Is Significant There are two common ways to define a function in JavaScript: // Function Statement function FUNCTION_NAME() { /* FUNCTION_BODY */ } // Function Expression var FUNCTION_NAME = function() { /* FUNCTION_BODY */ }; Although the function statement is less to type and is commonly used by those new to JavaScript, the behavior of the function expression is more straightforward. (Despite this, the Google style guide advocates using the function expression, so Closure uses it in almost all cases.) The behavior of the two types of function definitions is not the same, as illustrated in the following examples: function hereOrThere() { return 'here'; } alert(hereOrThere()); // alerts 'there' function hereOrThere() { return 'there'; } The Syntax for Defining a Function Is Significant | 523 www.it-ebooks.info It may be surprising that the second version of hereOrThere is used before it is defined. This is due to a special behavior of function statements called hoisting, which allows a function to be used before it is defined. In this case, the last definition of hereOr There() wins, so it is hoisted and used in the call to alert(). By comparison, a function expression associates a value with a variable, just like any other assignment statement. Because of this, calling a function defined in this manner uses the function value most recently assigned to the variable: var hereOrThere = function() { return 'here'; }; alert(hereOrThere()); // alerts 'here' hereOrThere = function() { return 'there'; }; For a more complete argument of why function expressions should be favored over function statements, see Appendix B of Douglas Crockford’s JavaScript: The Good Parts (O’Reilly). What this Refers to When a Function Is Called When calling a function of the form foo.bar.baz(), the object foo.bar is referred to as the receiver. When the function is called, it is the receiver that is used as the value for this: var obj = {}; obj.value = 10; /** @param {...number} additionalValues */ obj.addValues = function(additionalValues) { for (var i = 0; i < arguments.length; i++) { this.value += arguments[i]; } return this.value; }; // Evaluates to 30 because obj is used as the value for 'this' when // obj.addValues() is called, so obj.value becomes 10 + 20. obj.addValues(20); If there is no explicit receiver when a function is called, then the global object becomes the receiver. As explained in “goog.global” on page 47, window is the global object when JavaScript is executed in a web browser. This leads to some surprising behavior: 524 | Appendix B: Frequently Misunderstood JavaScript Concepts www.it-ebooks.info var f = obj.addValues; // Evaluates to NaN because window is used as the value for 'this' when // f() is called. Because and window.value is undefined, adding a number to // it results in NaN. f(20); // This also has the unintentional side effect of adding a value to window: alert(window.value); // Alerts NaN Even though obj.addValues and f refer to the same function, they behave differently when called because the value of the receiver is different in each call. For this reason, when calling a function that refers to this, it is important to ensure that this will have the correct value when it is called. To be clear, if this were not referenced in the function body, then the behavior of f(20) and obj.addValues(20) would be the same. Because functions are first-class objects in JavaScript, they can have their own methods. All functions have the methods call() and apply() which make it possible to redefine the receiver (i.e., the object that this refers to) when calling the function. The method signatures are as follows: /** * @param {*=} receiver to substitute for 'this' * @param {...} parameters to use as arguments to the function */ Function.prototype.call; /** * @param {*=} receiver to substitute for 'this' * @param {Array} parameters to use as arguments to the function */ Function.prototype.apply; Note that the only difference between call() and apply() is that call() receives the function parameters as individual arguments, whereas apply() receives them as a single array: // When f is called with obj as its receiver, it behaves the same as calling // obj.addValues(). Both of the following increase obj.value by 60: f.call(obj, 10, 20, 30); f.apply(obj, [10, 20, 30]); The following calls are equivalent, as f and obj.addValues refer to the same function: obj.addValues.call(obj, 10, 20, 30); obj.addValues.apply(obj, [10, 20, 30]); However, since neither call() nor apply() uses the value of its own receiver to substitute for the receiver argument when it is unspecified, the following will not work: // Both statements evaluate to NaN obj.addValues.call(undefined, 10, 20, 30); obj.addValues.apply(undefined, [10, 20, 30]); What this Refers to When a Function Is Called | 525 www.it-ebooks.info The value of this can never be null or undefined when a function is called. When null or undefined is supplied as the receiver to call() or apply(), the global object is used as the value for receiver instead. Therefore, the previous code has the same undesirable side effect of adding a property named value to the global object. It may be helpful to think of a function as having no knowledge of the variable to which it is assigned. This helps reinforce the idea that the value of this will be bound when the function is called rather than when it is defined. The var Keyword Is Significant Many self-taught JavaScript programmers believe that the var keyword is optional because they do not observe different behavior when they omit it. On the contrary, omitting the var keyword can lead to some very subtle bugs. The var keyword is significant because it introduces a new variable in local scope. When a variable is referenced without the var keyword, it uses the variable by that name in the closest scope. If no such variable is defined, a new binding for that variable is declared on the global object. Consider the following example: var foo = 0; var f = function() { // This defines a new variable foo in the scope of f. // This is said to "shadow" the global variable foo, whose value is 0. // The global value of foo could be referenced via window.foo, if desired. var foo = 42; var g = function() { // This defines a new variable bar in the scope of g. // It uses the closest declaration of foo, which is in f. var bar = foo + 100; return bar; }; // There is no variable bar declared in the current scope, f, so this // introduces a new variable, bar, on the global object. Code in g has // access to f's scope, but code in f does not have access to g's scope. bar = 'DO NOT DO THIS!'; // Returns a function that adds 100 to the local variable foo. return g; }; // This alerts 'undefined' because bar has not been added to the global scope yet. alert(typeof bar); // Calling f() has the side effect of introducing the global variable bar. var h = f(); alert(bar); // Alerts 'DO NOT DO THIS!' 526 | Appendix B: Frequently Misunderstood JavaScript Concepts www.it-ebooks.info // Even though h() is called outside of f(), it still has access to scope of // f and g, so h() returns (foo + 100), or 142. alert(h()); // Alerts 142 This gets even trickier when var is omitted from loop variables. Consider the following function, f(), which uses a loop to call g() three times. Calling g() uses a loop to call alert() three times, so you may expect nine alert boxes to appear when f() is called: var f = function() { for (i = 0; i < 3; i++) { g(i); } }; var g = function() { for (i = 0; i < 3; i++) { alert(i); } } // This alerts 0, 1, 2, and then stops. f(); Instead, alert() appears only three times because both f() and g() fail to declare the loop variable i with the var keyword. When g() is called for the first time, it uses the global variable i which has been initialized to 0 by f(). When g() exits, it has increased the value of i to 3. On the next iteration of the loop in f(), i is now 3, so the test for the conditional i < 3 fails, and f() terminates. This problem is easily solved by appropriately using the var keyword to declare each loop variable: for (var i = 0; i < 3; i++) Understanding var is important in avoiding subtle bugs related to variable scope. Enabling Verbose warnings from the Closure Compiler will help catch these issues. Block Scope Is Meaningless Unlike most C-style languages, variables in JavaScript functions are accessible throughout the entire function rather than the block (delimited by curly braces) in which the variable is declared. This can lead to the following programming error: /** * Recursively traverses map and returns an array of all keys that are found. * @param {Object} map * @return {Array.} */ var getAllKeys = function(map) { var keys = []; for (var key in map) { keys.push(key); var value = map[key]; if (typeof value == 'object') { Block Scope Is Meaningless | 527 www.it-ebooks.info // Here, "var map" does not introduce a new local variable named map // because such a variable already exists in function scope. var map = value; keys = keys.concat(getAllKeys(map)); } } return keys; }; var mappings = { 'derivatives': { 'sin x': 'cos x', 'cos x': '-sin x'}, 'integrals': { '2x': 'x^2', '3x^2': 'x^3'} }; // Evaluates to: ['derivatives', 'sin x', 'cos x', 'integrals'] getAllKeys(mappings); The array returned by getAllKeys() is missing the values '2x' and '3x^2'. This is because of a subtle error where map is reused as a variable inside the if block. In languages that support block scoping, this would introduce a new variable named map that would be assigned to value for the duration of the if block, and upon exiting the if block, the recent binding for map would be discarded and the previous binding for map would be restored. In JavaScript, there is already a variable named map in scope because one of the arguments to getAllKeys() is named map. Even though declaring var map within getAllKeys() is likely a signal that the programmer is trying to introduce a new variable, the var is silently ignored by the JavaScript interpreter and execution proceeds without interruption. When the Verbose warning level is used, the Closure Compiler issues a warning when it encounters code such as this. To appease the Compiler, either the var must be dropped (to indicate the existing variable is meant to be reused) or a new variable name must be introduced (to indicate that a separate variable is meant to be used). The getAllKeys() example falls into the latter case, so the if block should be rewritten as: if (typeof value == 'object') { var newMap = value; keys = keys.concat(getAllKeys(newMap)); } Interestingly, because the scope of a variable includes the entire function, the declaration of the variable can occur anywhere in the function, even after its first “use”: var strangeLoop = function(someArray) { // Will alert 'undefined' because i is in scope, but no value has been // assigned to it at this point. alert(i); // Assign 0 to i and use it as a loop counter. for (i = 0; i < someArray.length; i++) { alert('Element ' + i + ' is: ' + someArray[i]); } 528 | Appendix B: Frequently Misunderstood JavaScript Concepts www.it-ebooks.info // Declaration of i which puts it in function scope. // The value 42 is never used. var i = 42; }; Like the case in which redeclaring a variable goes unnoticed by the interpreter but is flagged by the Compiler, the Compiler will also issue a warning (again, with the Verbose warnings enabled) if a variable declaration appears after its first use within the function. It should be noted that even though blocks do not introduce new scopes, functions can be used in place of blocks for that purpose. The if block in getAllKeys() could be rewritten as follows: if (typeof value == 'object') { var functionWithNewScope = function() { // This is a new function, and therefore a new scope, so within this // function, map is a new variable because it is prefixed with 'var'. var map = value; // Because keys is not prefixed with 'var', the existing value of keys // from the enclosing function is used. keys = keys.concat(getAllKeys(map)); } }; // Calling this function will have the desired effect of updating keys. functionWithNewScope(); Although this approach will work, it is less efficient than replacing var map with var newMap as described earlier. Block Scope Is Meaningless | 529 www.it-ebooks.info www.it-ebooks.info APPENDIX C plovr plovr is a build tool for projects that use Closure. (Its name is the product of the animal on the cover of this book, the Web 2.0 zeitgeist, and domain name availability.) It can be run either as a web server (like the Closure Compiler Service introduced in Chapter 12), or as a command-line build script (like calcdeps.py). The goal of plovr is to make development with Closure easier and faster. By integrating the Closure Library, Templates, and Compiler into a single application with a normalized set of input options, it is considerably easier to get up and running with Closure from a single download. Furthermore, because plovr can be run as a web server, it can store information from previous compilations in memory, making subsequent compilations faster. When running plovr as a web server, it provides a number of services that can help with development: • Displays warnings and errors from the Compiler at the top of the web page that loads the compiled JavaScript with links to the line number where the warning or error occurred. • Automatically compiles Soy files (as indicated by files whose names end in .soy) to JavaScript files. • Provides a view that compares the size of the original code to that of the compiled code. This is done on a per-file basis so that it is possible to identify which input files are contributing the most code to the compiled output. • Generates an externs file from the calls to goog.exportSymbol() and goog.export Property() in the input. • Generates the source map needed to use the deobfuscated debugging feature in the Closure Inspector. • Partitions code into modules, as described in “Partitioning Compiled Code into Modules” on page 363. This feature became available in plovr just before publication, so check http://plovr.com for the latest documentation on this feature. 531 www.it-ebooks.info Getting Started with plovr Like the Closure Compiler and Closure Templates, plovr is written in Java and is opensourced under the Apache 2.0 license. Its source code is available at http://code.google .com/p/plovr/. Binary distributions can also be downloaded from the Google Code page under the Downloads tab. At the time of this writing, plovr is still in its early stages, so its API has not been finalized. If the latest version of plovr does not work with the examples in this appendix, be sure to check http://plovr.com or the wiki on the Google Code page for any API changes. To start using plovr, download the latest binary from the website, which is an executable jar file—running plovr requires Java 1.6 or later. The jar will have some sort of version number or revision number baked into its name, but the examples in this appendix will assume that it is simply named plovr.jar. To make sure that everything is working, run: java -jar plovr.jar --help This should print a usage message that looks something like: plovr build tool basic commands: build serve compile the input specified in a config file start the plovr web server As indicated by the usage message, plovr supports two commands: build and serve. To see the options that an individual command takes, run the command with the --help flag: # This will print out the options for the serve command. java -jar plovr.jar serve --help Both the build and serve commands require a config file as an argument, so the next section will explain config files, followed by sections on the build and serve commands, respectively. Config Files In plovr, a config file is used to specify the inputs and options for compilation. At first, it may seem less convenient to specify options via a config file rather than via the command line. However, the list of options to plovr can be long, and their values are unlikely to change once they are set up. Because of this, it is easier to create a config file once and use it repeatedly than it is to remember a long list of flags that need to be specified every time that plovr is used. For the limited set of options whose values are likely to 532 | Appendix C: plovr www.it-ebooks.info be toggled during development, it is possible to redefine them using query data, as explained in “Serve Command” on page 535. The format for a config file is a superset of JSON that has limited support for comments. The following is the contents of hello-config.js, a plovr config file for the “Hello World” example from Chapter 1: { } "id": "hello-world", "inputs": "hello.js", "paths": "." A config must have a unique id, which is specified by the id property, so in this case the id is hello-world. It must also have at least one input file to compile, which is specified by the inputs property, so the input for hello-config.js is hello.js. The paths property in the config identifies paths where potential dependencies can be found. In this way, both inputs and paths act the same as the respective --input and --path flags to calcdeps.py. Both inputs and paths can have multiple values, so each may be an array of strings rather than a single string. Each value can identify either a file or a directory, and specifying a directory includes JavaScript and Soy files in its subdirectories. Recall from Chapter 1 that both hello.js and hello.soy are in the same directory, so this helloconfig.js could also be defined as: { } "id": "hello-world", "inputs": "hello.js", "paths": ["hello.js", "hello.soy"] Unlike the case where calcdeps.py is used, there is no need to include ../closure/ library or ../closure-templates/javascript/soyutils_usegoog.js as values for paths. This is because plovr.jar includes the JavaScript source code for both the Closure Library and soyutils_usegoog.js and always includes them as potential dependencies. To use your own version of the Closure Library instead of the one that is bundled with plovr, add a property named closure-library in the config that identifies your path to the Library as a string. This can be helpful if you are making changes to the Library, or if you want to be able to insert debugger statements into the Library’s source code. Finally, //-style comments may appear in a config file if they are on their own line. (This simplification makes them easier to strip before passing them to the JSON parser.) This means that hello-config.js could be documented as follows: Config Files | 533 www.it-ebooks.info { } // This comment is allowed in a plovr config file, but it would not be // allowed by a strict JSON parser, like the ones built into newer web // browsers. "id": "hello-world", "inputs": "hello.js", "paths": "." However, the following would not be allowed in plovr (though it would be allowed in JavaScript) because the comment is not on its own line: { } "id": "hello-world", "inputs": "hello.js", "paths": "." // This is not a valid comment in plovr. Because //-style comments are not supported by JSON, the recommended way of creating a comment is to add a dummy property as follows: { } "comment": "Now I can have a comment" However, this is really tedious if you want to write a detailed comment without using long lines. It is not possible to break up long strings with the + operator as you would in JavaScript, because that would not be valid JSON, either. The best remaining option is to use an array of strings: { } "comment": ["This is a lot of work to insert a multi-line comment.", "It is really unfortunate that this is the best option."] Therefore, it would be very frustrating to use plovr config files if //-style comments were not allowed. Build Command When using the Closure Library and Templates together, compiling JavaScript often requires three steps: first Templates must be compiled to JavaScript, then JavaScript inputs are topologically sorted based on their dependencies, and finally the JavaScript files are compiled together using the Closure Compiler. In plovr, all of these steps are performed behind the scenes, so compilation is done simply by using the build command with the appropriate config file: java -jar plovr.jar build hello-config.js Running this command will compile hello.js (and all of its dependencies) and print the result to standard out. Behind the scenes, plovr is responsible for compiling 534 | Appendix C: plovr www.it-ebooks.info any .soy files specified by paths or inputs in the config and compiling them to JavaScript files. It then performs the sorting and compilation steps automatically. By default, code is compiled using the Compiler’s Simple compilation mode and Default warning level. These settings can be overridden in the config file using the mode and level properties: { } "id": "hello-world", "inputs": "hello.js", "paths": ".", "mode": "ADVANCED", "level": "VERBOSE" The mode property corresponds to the compilation mode to use, which must be one of ADVANCED, SIMPLE, WHITESPACE, or RAW. The first three options correspond to the acceptable inputs to the --compilation_level flag of the Closure Compiler (though the names of the plovr equivalents are more concise). The RAW option cannot be used with the build command (if it is used, it will produce an error), but it can be used with the serve command, as explained in the next section. Serve Command Although plovr is handy as a command-line build script for Closure, it is far more useful when used as a compilation server during Closure development. To start plovr as a web server, run: java -jar plovr.jar serve hello-config.js Once it is running, point your browser to http://localhost:9810/compile?id=helloworld. When the /compile URL is loaded, plovr will find the config specified by the id in the query parameters (which is hello-world in this case) and then compile the inputs specified in the config, using the dependencies in paths, as necessary. In the case of hello-config.js, paths is the current directory, so if new JavaScript or Soy files are added to the directory while plovr is running, it will automatically include them when the /compile URL is reloaded: there is no need to restart plovr to pick up the new files. As explained in the previous section, plovr uses Simple compilation mode with Default warnings by default. In addition to overriding these settings in the config file, it is also possible to redefine them using query parameters to the /compile URL. For example, loading http://localhost:9810/compile?id=hello-world&mode=advanced&level=ver bose will compile hello.js in Advanced mode with Verbose warnings. (In both the config file and the query parameters, values for mode and level are case-insensitive.) Specifying the value in a query parameter will override the value set in the config file. This means that during development, the Now as you edit hello.js and hello.soy, reloading hello.html will automatically recompile your input and load the new result. Further, if hello.html is loaded from a web server, it is also possible to add query parameters to hello.html to change the compilation level and mode used by plovr. For example, if hello.html were being served on localhost on port 8080, loading http://localhost:8080/hello.html?mode=advanced would load hello.js compiled in Advanced mode. This is because the /compile URL will also use the query parameters of its referrer (which in this case, is http://localhost: 8080/hello.html?mode=advanced) to override the default settings specified by the config. (The query parameters of the referrer supercede those of the request URL.) You can see more examples of this on the live plovr demo page at http://plovr.com/demo/. When a browser loads a local file, it does not send a referrer when fetching resources, so if file:///C:/closure/hello-world/hello.html? mode=advanced were loaded, plovr would still use its default settings because it would not receive file:///C:/closure/hello-world/hello. html?mode=advanced as a referrer. However, it would still be possible to edit the query parameters used in the src of the

Navigation menu