Tuesday, December 31, 2013

Polymer, External Libraries, and Latency


One of the many things I appreciate about Polymer is how it handles getting everything ready. There are a lot of interconnected parts that can be loaded in a Polymer application or widget, but Polymer manages them with aplomb. This is principally accomplished via the WebComponentsReady event which Polymer generates and uses—mostly in the form a nice, simple animated reveal of all Polymers when they are ready to be used.

I am unsure, however, how this WebComponentsReady strategy works when a Polymer is also dependent on third party libraries. I just so happen to be working on a Polymer that depends on Underscore.js. When everything is run locally with zero latency this seems to work just fine. But how will my initial approach fair under more realistic conditions?

From last night, my Polymer is pulling in Underscore.js from the same local web server that hosts my application and my Polymer code. The Polymer does this inside the defining <polymer-element>:
<link rel="import" href="../bower_components/polymer/polymer.html">
<polymer-element name="hello-you">
  <template><!-- ... --></template>
  <script src="../bower_components/underscore/underscore.js"></script>
  <script>
    // Polymer element code here ...
  </script>
</polymer-element>
Currently, this produces network request like this:



My hello-you.html Polymer definition <link>-imports the polymer.html definition and the Underscore library. As soon as the relatively small polymer.html is imported, it requests polymer.js, which contains all of the necessary Polymer library code. While polymer.js is loading, Underscore.js completes loading and is evaluated, making it available when my Polymer code is evaluated. And this is important because my Polymer uses underscore as soon as it is ready:
<link rel="import" href="../bower_components/polymer/polymer.html">
<polymer-element name="hello-you">
  <template>
    <!-- ... -->
  </template>
  <script src="../bower_components/underscore/underscore.js"></script>
  <script>
    Polymer('hello-you', {
      your_name: '',
      // ...
      feelingLucky: _.debounce(
        // A function in need of debouncing...
        750,
        true
      )
    });
  </script>
</polymer-element>
So what happens if there is a ton of latency (as in a mobile connection)? Will my Polymer class definition crash because the _ Underscore.js top-level variable is not defined?

That turns out to be a nearly impossible question to answer directly. Even though Underscore.js is larger than Polymer.js, the indirection of importing polymer.html which then imports polymer.js turns out to be sufficient to make it impossible to break my Polymer. Even if I create latency of up to 1.6 seconds:
$ sudo tc qdisc add dev lo root netem delay 800ms
(always, always, always remember to sudo tc qdisc del dev lo root when done testing latency)

Even this is not enough to make the evaluation times different enough so I can determine if Underscore.js is not available for Polymer. So instead, I create a gigantic JavaScript file that is mostly comment filler, but defines a single variable that I use in my Polymer. And, when I load the script I find:



That is beautiful. Even though the filler script takes significantly longer to load than Polymer does, Polymer is aware of the need to wait and does not do its thing until the <script> tag is ready.

The other question that I would like to answer has more to do with deployment—how does latency affect third party libraries when they are hosted on CDNs. It so happens that Underscore.js is hosted on a CDN, so I switch my <script> tag to point to such a CDN:
<link rel="import" href="../bower_components/polymer/polymer.html">
<polymer-element name="hello-you">
  <template><!-- ... --></template>
  <script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.5.2/underscore-min.js"></script>
  <script>
    // Polymer element code here ...
  </script>
</polymer-element>
Now, I can add insane latency to my network connection, leaving the localhost connection effectively instantaneous:
$ sudo tc qdisc add dev eth1 root netem delay 10000ms
With that, I still see Polymer doing the right thing:



It takes 20 seconds to load underscore from that CDN because of my absurd traffic control (tc) setting. And yet Polymer just works. It waits until the library is loaded and then reveals the fully working Polymer element.

How cool is that?

One final note here is that it does not seem to matter where the <script> tag goes. Polymer still does right when the <script> tag goes at the top of the Polymer definition:
<link rel="import" href="../bower_components/polymer/polymer.html">
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.5.2/underscore-min.js"></script>
<polymer-element name="hello-you">
  <template><!-- ... --></template>
  <script>
    // Polymer element code here ...
  </script>
</polymer-element>
I think that serves better to illustrate the immediate dependencies of a Polymer—something akin to declaring “normal” code's imports at the top of a file. Since there does not appear to be a difference in functionality or performance, I will likely opt for that approach in Patterns in Polymer.

I do love it when I discover that libraries and tools just work like this. Maybe someday I can write tools that behave this well, but until then I will have to content myself with being able to appreciate them. And write lots of books extolling their many virtues!



Day #982

1 comment: