Using Polymer with Closure Compiler - Part 1: Type Information
Wednesday, June 15, 2016 | 1:01 PM
This is the first post in a 3-part series about using Polymer with Closure Compiler
Introduction
Closure Compiler has long been practically the only JavaScript compiler to be able to rename properties (using the
ADVANCED
optimization level). In addition, it offers an impressive amount of static analysis and type checking while still
writing in native
ECMAScript. However, with the adoption of frameworks with data-bound templates such as Angular, the power of Closure
Compiler
has been significantly reduced because the template references are external to the compiler.
With Polymer, it is possible to maintain a high degree of property renaming and type checking while still utilizing the power of data-bound HTML templates. Finding information on how to use the two together has been difficult thus far.
This post explains how type information in the compiler is created from Polymer element definitions and how to utilize them. Part 2 concentrates on how to obtain optimal renaming of Polymer elements and part 3 will detail how to rename data-binding references in the HTML templates consistently with the element properties.
The Polymer Pass
Closure Compiler has a pass specifically written to process Polymer element definitions and produce the correct type
information.
The pass is enabled by specifying the --polymer_pass
command line flag. The pass allows the rest of the
compiler to
properly understand Polymer types.
Polymer element definitions contain both standard properties and can also declare properties on a special properties
object. It can be confusing to understand the difference. Both will end up as properties on the created class’
prototype. However,
if you have a property which does not need the extra abilities of the properties
object, where does it
go? The official
guidance has been if the property is part of the public API for an element, it should be defined on the properties
object. However, with Closure Compiler, it’s not quite so cut-and-dry.
Declared Properties - Children of the properties
Object
Declared properties biggest advantage is how they work behind the scenes. Polymer attaches them using getters and setters so that any change to the property automatically updates data-bound expressions. However, because these elements can also be serialized as attributes, referenced from CSS and are generally considered external interfaces, the compiler will never rename them. The Polymer pass of the compiler creates an external interface and marks the Polymer element as implementing that interface. This blocks renaming without incurring the loss of type checking that happens with properties which are quoted.
Standard Properties
Standard properties on the other hand are potentially renamable (the compiler will use its standard strategies to
determine whether it is safe to rename the property or not). In fact, one method to prevent template references from
being broken by renaming is to quote all standard properties used in data-binding expressions. This is less than
ideal and part 3
of the series will describe how to avoid this. Standard properties are still accessible from outside of the
component and can also be considered part of the public API of an element, but they retain the ability to be
renamed. However, because they are not defined with Polymer’s getters and setters, you must either use the Polymer
set
method to make
changes to the property or use either notifyPath
or notifySplices
to inform Polymer that a change has already occurred. The next post in the series talks about how to use these
methods with renamed properties.
Behaviors
Polymer behaviors are essentially a mixin. Since the compiler needs explicit knowledge of behavior implementation, type definitions are copied onto the element. The Polymer pass of Closure Compiler automatically creates sub entries for each behavior method. There are a some not-so-obvious implementation details however:
- Behaviors must be defined as object literals - just like a Polymer element definition.
- Behaviors must be annotated with the special
@polymerBehavior
annotation. - Methods of a behavior should be annotated with
@this {!PolymerElement}
so that the compiler knows the correct type of thethis
keyword. - Behaviors must be defined as a global type name - and cannot be aliased.
Element Type Names
Most Polymer examples call the Polymer
function without using the return value. In these cases, the Polymer pass will automatically create a type name for
the element from the tag name. The tag name is converted from a hyphenated name to an upper camel case name and the
word Element
is appended. For instance, the compiler sees the foo-bar
element definition and creates a global FooBarElement
type. This allows references in other elements to type cast the value.
var fooBarElement = /** @type {FooBarElement} */(this.$$('foo-bar'));Authors may also choose to assign their own type name. To do that, simply use the return value of the
Polymer
function.
myNamespace.FooBar = Polymer({is:'foo-bar'});The assigned name will now be the type name:
var fooBarElement = /** @type {myNamespace.FooBar} */(this.$$('foo-bar'));Because type names must be globally accessible, the Polymer pass will only recognize this assignment in the global scope or if the name is a qualified namespace assignment.
Summary
This post describes how Closure Compiler processes Polymer element definitions. In many cases, the compiler will automatically process and infer type information. However, Polymer elements must still be written in a way which is compatible with the restrictions imposed by Closure Compiler. Don’t assume that any element you find will automatically be compatible. If you find one that isn’t, may I suggest a pull request?
0 comments:
Post a Comment