Ini hal memalukan yang terjadi kalau lo tumbang. Tonton & jangan sampai ini terjadi ke lo!
Astaghfirullah, Kembang Api Berbahan Baku Al-Qur'an Beredar Di Tengah Masyarakat, Bantu
This is the last post in a 3-part series about using Polymer with Closure Compiler
With Closure-Compiler in ADVANCED
mode, the concept of “whole world” optimization is used. Simply stated, the compiler needs to know about all of the JavaScript source used and all the ways it can be consumed by other libraries/event handlers/scripts.
Polymer templates would logically be thought of as an external use case. However, symbols referenced externally can't be renamed by the compiler. So, we need to provide the Polymer templates to Closure-Compiler along with our script source so that everything can be renamed consistently. Problem is, Polymer templates are HTML not JavaScript.
Polymer-rename was created to solve just this problem. It works by translating the HTML template data-binding expressions to JavaScript before compilation and then reverses the process afterwards.
The polymer-rename extract plugin parses the HTML of a Polymer element. It ignores the content of <script>
and <style>
tags. It looks for polymer expressions such as:
<button on-tap="tapped_">[[buttonName]]</button>
From these expressions, it generates matching JavaScript:
polymerRename.eventListener(16, 23, this.tapped_);
polymerRename.symbol(27, 37, this.buttonName);
This JavaScript is not designed to ever be executed directly. You don't package it with your elements. Instead, Closure-Compiler uses this to consistently rename the symbols.
To separate your Polymer element script source from the templates and to provide all the source to Closure-Compiler in the correct order, use the vulcanize tool to combine all of your elements and inline all of the scripts. Next, use the crisper tool to extract all of your inline scripts into a single external javascript file. If you want your script inlined after compilation, just use the vulcanize tool again.
With your polymer-rename generated script and your extracted source from vulcanize and cripser, you can now use Closure-Compiler. By default, Closure-Compiler is going to combine all of your JavaScript into a single output file. But we do not want the polymer-rename generated code packaged with the rest of our scripts. Closure-Compiler has a powerful - yet confusing to use - code splitting functionality which will allow us to direct some JavaScript to a different output file. The confusing part is that the flag to trigger the code splitting is named “module”. Don't mistake this with input module formats like ES6, CommonJS or goog.module - it has nothing to do with these.
Here's an example compile command (using the compiler gulp plugin):
const closureCompiler = require('google-closure-compiler');
gulp.task('compile-js', function() {
gulp.src([
'./src/js/element-source.js',
'./build/element-template-expressions.js'])
.pipe(closureCompiler({
compilation_level: 'ADVANCED',
warning_level: 'VERBOSE',
polymer_pass: true,
module: [
'element-source:1',
'element-template-expressions:1:element-source'
],
externs: [
require.resolve(
'google-closure-compiler/contrib/externs/polymer-1.0.js'),
require.resolve(
'polymer-rename/polymer-rename-externs.js')
]
})
.pipe(gulp.dest('./dist'));
});
What's going on here? We provided exactly two javascript files to Closure-Compiler - and the order matters greatly. The first module definition consumes 1 javascript file (that's the :1
part of the flag). The second module flag also consumes 1 javascript file and logically depends on the first module. The code-splitting flags are a bit unwieldy as they require you to have an exact count of your input files and to make sure they are apportioned between your module flags correctly - and in the right order.
After compilation completes, the “dist” folder should have two javascript files: element-source.js and element-template-expressions.js. The element-template-expressions.js file should only contain the template expressions extracted by the polymer-rename project, but now with all of the symbol references properly renamed.
Now it's time to go back and update the original HTML templates with our newly renamed expressions. There's not a lot to this step - just call the polymer-rename replace plugin and watch it work. The example Polymer HTML expression from earlier might now look something like:
<button on-tap="a">[[b]]</button>
In part 1 of the series, I discussed how the Polymer pass of Closure-Compiler generates type names based off the element tag name: <foo-bar>
by default will have the type name FooBarElement
. However, I also explained that an author can assign the return value of the Polymer
function to specify custom type names. The polymer-rename plugins will use the same logic to determine type names. If any of your elements have custom type names you will need to provide those names to the extract plugin of polymer-rename.
The extract plugin optionally takes a function which is used to lookup these names. Here's an example implementation of a custom lookup function:
/**
* Custom element type name lookup
* @param {string} tagName
* @return {string|undefined}
*/
function lookupTypeByTagName(tagName) {
if (/^foo(-.*)/.test(tagName)) {
return 'myNamespace.Foo' + tagName.replace(/-([a-z])/g,
function(match, letter) {
return letter.toUpperCase();
});
}
// returning undefined here causes the polymer-rename
// plugin to fall back to the default
// behavior for type name lookups.
return undefined;
}
In this implementation, any element that starts with foo-
will have a type name that is upper camel case and a member of the myNamespace
object.
In addition to allowing full renaming of Polymer elements by Closure-Compiler, the polymer-rename plugin also enables a wide range of type checking. The compiler can now see how Polymer computed property methods are called - and will properly notify you if the number of arguments or types don't match.
Closure-Compiler ADVANCED
optimizations and Polymer can create a powerful app, it just takes a little work and an understanding of how they fit together.
Posted by Chad Killingsworth, JS Engineer for Jack Henry and Associates, Closure Compiler committer
This is the second post in a 3-part series about using Polymer with Closure Compiler
UPDATE: goog.reflect.objectProperty
is now available as part of the 20160619 compiler and library releases.
Closure Compiler's ADVANCED
mode property renaming and dead code elimination put it in a class all its own. In ADVANCED
mode, the compiler performs “whole world” optimizations. Polymer apps can take advantage of these optimizations without losing functionality.
Closure Compiler property renaming occurs in two primary ways. The first is quite straightforward: all properties with the same name are renamed in the same way. This is ideal because it doesn't require any type information to work. All instances of .foo
are renamed .a
regardless of on which object they are defined. However, if any property with the same name is found on any object in the externs, the compiler cannot rename it with this strategy. The more extern properties included in your compilation, the fewer properties can be renamed with this method.
The second method for property renaming was created to address the shortcomings in the first method. Here, the compiler uses type information to rename properties so that they are unique. This way, the first method can happily rename them as they no longer share the name of an extern property. This method is called type-based renaming and as its name suggests, it can only work with proper type information. It will decline to rename a property if it finds the same property on an object for which it cannot determine type information. The better type information provided, the better this method works.
Finally, for property renaming to work at all, properties must be consistently referenced. Properties accessed using an array style bracket notation (such as foo['bar']
) are called quoted properties and they will never be renamed. Properties accessed using a dot notation (such as foo.bar
) are called dotted properties and may be renamed. Your code can break if you access the same property using both methods - so make sure you choose one and are consistent.
The Polymer library itself is considered an external library. A well maintained externs file for Polymer is hosted within the compiler repository (and distributed in the npm version). Lifecycle methods (such as created
, ready
, attached
, etc) are externally defined and therefore not renameable. Also, as mentioned in part 1 of this series , declared properties defined as part of Polymer's properties
object can never be renamed.
That leaves non-lifecycle standard properties as eligible for renaming - as long as they are not quoted. However, since Polymer's listeners and observers are specified as strings, that breaks the consistent access rule for properties and forces you to quote those properties. There are, however, other options.
A Polymer element declares a property observer like:
Polymer({
is: 'foo-bar',
properties: {
foo: {
type:String,
observer: 'fooChanged_'
}
}
/** @private */
'fooChanged_': function(oldValue, newValue) {}
});
In this case, our fooChanged_
method is a private implementation detail. Renaming it would be ideal. However for that to be possible, we would need to have access to the renamed name of fooChanged_
as a string. Closure Library has a primitive that Closure Compiler understands to help in just this case: goog.reflect.object.
By using goog.reflect.object
we can rename the keys of an object literal in the same way that our Polymer element is renamed. After renaming, we can use goog.object.transpose to swap the object keys and values enabling us to easily lookup the name of our now renamed property.
var FooBarElement = Polymer({
is: 'foo-bar',
properties: {
foo: {
type: String,
observer: FooBarRenamedProperties['fooChanged_']
}
}
/** @private */
fooChanged_: function(oldValue, newValue) {}
});
var FooBarRenamedProperties = goog.object.transpose(
goog.reflect.object(FooBarElement, {
fooChanged_: 'fooChanged_'
})
);
We can use the same technique to rename listener methods:
var FooBarElement = Polymer({
is: 'foo-bar',
listeners: {
'tap': FooBarRenamedProperties['tapped_']
}
/** @param {!Event} evt */
tapped_: function(evt) {}
});
var FooBarRenamedProperties = goog.object.transpose(
goog.reflect.object(FooBarElement, {
tapped_: 'tapped_'
})
);
Polymer provides three different methods to indicate that a property has changed and data-binding expressions should be re-evaluated: set
, notifyPath
and notifySplices
. All three have one unfortunate thing in common: they require us to specify the property name as a string. This would also break the consistent access rule for properties and once again we need access to the renamed property as a string. While the goog.object.transpose(goog.reflect.object(typeName, {}))
technique would also work for this case, it requires us to know the globally accessible type name of the object. In this case, Closure Library has another primitive to help: goog.reflect.objectProperty . This method is very new. As of this writing, goog.reflect.objectProperty
has yet to be released in either Closure Compiler or Closure Library (though it should be soon). goog.reflect.objectProperty
allows us to call the notification methods with a renamed string.
Polymer({
is: 'foo-bar',
baz:'Original Value',
attached: function() {
setTimeout((function() {
this.baz = 'New Value';
this.notifyPath(
goog.reflect.objectProperty('baz', this), this.baz);
}).bind(this), 1000);
}
});
goog.reflect.objectProperty
simply returns the string name (first argument) in uncompiled mode. Its use comes solely as a Closure Compiler primitive where the compiler replaces the entire call with simply a string of the renamed property.
By reserving Polymer's declared properties for cases where the special functionality offered is actually needed, quite a bit of renaming can be obtained on an element. In addition, use of goog.reflect.object
and goog.reflect.objectProperty
allows us to rename properties which are required to be used with strings.
However now we find ourselves in a case where all this renaming has broken references in our template data-binding expressions. Time for Part 3: Renaming in Polymer Templates.
Posted by Chad Killingsworth, JS Engineer for Jack Henry and Associates, Closure Compiler committer
This is the first post in a 3-part series about using Polymer with Closure Compiler
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.
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.
properties
ObjectDeclared 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 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.
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:
@polymerBehavior
annotation. @this {!PolymerElement}
so that the compiler knows the correct type of the this
keyword. 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 style="box-sizing: border-box; color: rgb(167 , 29 , 93);">=Authors may also choose to assign their own type name. To do that, simply use the return value of the
/** style="box-sizing: border-box; color: rgb(167 , 29 , 93);">@type {FooBarElement} */( style="box-sizing: border-box; color: rgb(237 , 106 , 67);">this. style="box-sizing: border-box; color: rgb(121 , 93 , 163);">$$( style="box-sizing: border-box; color: rgb(24 , 54 , 145);">'foo-bar style="box-sizing: border-box;">'));
Polymer
function.myNamespace.FooBar style="box-sizing: border-box; color: rgb(167 , 29 , 93);">= style="box-sizing: border-box; color: rgb(121 , 93 , 163);">Polymer({is style="box-sizing: border-box; color: rgb(167 , 29 , 93);">: style="box-sizing: border-box; color: rgb(24 , 54 , 145);">'foo-bar style="box-sizing: border-box;">'});The assigned name will now be the type name:
var fooBarElement style="box-sizing: border-box; color: rgb(167 , 29 , 93);">=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.
/** style="box-sizing: border-box; color: rgb(167 , 29 , 93);">@type {myNamespace.FooBar} */( style="box-sizing: border-box; color: rgb(237 , 106 , 67);">this. style="box-sizing: border-box; color: rgb(121 , 93 , 163);">$$( style="box-sizing: border-box; color: rgb(24 , 54 , 145);">'foo-bar style="box-sizing: border-box;">'));
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?
Posted by Chad Killingsworth, JS Engineer for Jack Henry and Associates, Closure Compiler committer