Wednesday, March 25, 2015

Custom Element API Generation for Polymer.dart


You could say that I've forgotten more about Polymer than most programmers will ever know. I would probably agree, but that's just because I have a crap memory.

I was able to get the <google-map> JavaScript Polymer element to work in a Polymer.dart context yesterday, but only by hand-editing the JavaScript source files that I had installed via Bower—not at all cool. Happily, James Hurford reminded me that I should be using custom_element_apigen to wrap JavaScript Polymer elements in Dart.

I have looked at it in the past, but in my defense, it caused me grief. So maybe it was repression instead of a failing memory. Let's stick with that story, shall we?

I give custom_element_apigen another try tonight by first adding it to the list of development dependencies in my sample application:
name: my_maps
dependencies:
  polymer: any
dev_dependencies:
  custom_element_apigen: any
transformers:
- polymer:
    entry_points:
    - web/index.html
I have to update the location in which Bower installs its packages so that custom_element_apigen can find the elements that I want to use in my Dart application. So I modify the .bowerrc configuration file to install everything in lib/src:
{
  "directory": "lib/src"
}
Installing now places these dependencies in lib/src as desired:
$ bower install
...
polymer#0.5.1 lib/src/polymer
├── core-component-page#0.5.5
└── webcomponentsjs#0.5.1

google-map#0.4.1 lib/src/google-map
└── google-apis#0.4.4  

webcomponentsjs#0.5.1 lib/src/webcomponentsjs

core-component-page#0.5.5 lib/src/core-component-page
├── polymer#0.5.1
└── webcomponentsjs#0.5.1

google-apis#0.4.4 lib/src/google-apis
└── core-shared-lib#0.5.5

core-shared-lib#0.5.5 lib/src/core-shared-lib
└── polymer#0.5.1
With that, I am ready to create a configuration file for custom_element_apigen. The project is still woefully (and self-admittedly) under-documented. Thankfully, I still have my old configuration file from the last time, so I adapt it for <google-map>. I save the following in apigen.yaml:
files_to_generate:
  - google-map/google-map.html
I then run the generate command. In addition to lack of documentation, it also lacks output. Even so, it does not take too much work to find the generated files:
$ pub run custom_element_apigen:update apigen.yaml 
Done                                      

$ ls -ltr lib
total 32
drwxr-xr-x 2 chris chris 4096 Mar 24 23:57 assets
drwxr-xr-x 2 chris chris 4096 Mar 25 23:04 elements
drwxr-xr-x 8 chris chris 4096 Mar 25 23:04 src
-rw-r--r-- 1 chris chris  114 Mar 25 23:07 google_map_nodart.html
-rw-r--r-- 1 chris chris  164 Mar 25 23:07 google_map.html
-rw-r--r-- 1 chris chris 9065 Mar 25 23:07 google_map.dart
Based on the nodart file, it seems that custom_element_apigen supports both the new and old style importing approaches. In my sample application page, I opt for the new, code-import style:
<!doctype html>
<html lang="en">
  <head>
    <script type="application/dart">
      import 'package:my_maps/google_map.dart';
      export 'package:polymer/init.dart';
    </script>
  </head>
  <body unresolved>
    <google-map
       latitude="37.779"
       longitude="-122.3892"
       minZoom="9"
       maxZoom="11"
       fit></google-map>
  </body>
</html>
When I try to run my simple application, pub serve reports:
[Error from polymer (PolymerBootstrapTransformer) on my_maps|web/index.html]:
Could not load asset my_maps|lib/google_maps_api.dart
[Warning from polymer (PolymerBootstrapTransformer) on my_maps|web/index.html]:
line 2, column 1 of lib/google_map_nodart.html: Failed to inline HTML import: Could not find asset my_maps|lib/google_maps_api_nodart.html.
null. See http://goo.gl/5HPeuP#web_components_4 for details.
<link rel="import" href="google_maps_api_nodart.html">
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
So I update my apigen.yaml file to include this element from the Bower-installed JavaScript dependency:
files_to_generate:
  - google-map/google-map.html
  - google-apis/google-maps-api.html
I re-run pub run custom_element_apigen:update apigen.yaml, restart pub serve, and it fails again. This time with a different element. I repeat this process until my agigen.yaml file contains:
files_to_generate:
  - google-map/google-map.html
  - google-apis/google-maps-api.html
  - core-shared-lib/core-shared-lib.html
I again re-run pub run custom_element_apigen:update apigen.yaml, again restart pub serve, and finally, I have a working <google-map> in a Polymer.dart application:



Nice.

I had used explicit Bower dependencies last night in a attempt to eliminate webcomponentjs.js incompatibilities between my Polymer.dart code and my Polymer.js elements:
{
  "name": "my-maps",
  "dependencies": {
    "google-map": "GoogleWebComponents/google-map#0.4.1",
    "polymer": "Polymer/polymer#0.5.1",
    "webcomponentsjs": "Polymer/webcomponentsjs#0.5.1"
  }
}
I relax those dependencies now:
{
  "name": "my-maps",
  "dependencies": {
    "google-map": "GoogleWebComponents/google-map#~0.4.2",
    "polymer": "Polymer/polymer#~0.5.5",
    "webcomponentsjs": "Polymer/webcomponentsjs#~0.5.5"
  }
}
After a re-install of the Bower dependencies, I have:
$ bower install
...
webcomponentsjs#0.5.5 lib/src/webcomponentsjs

google-map#0.4.2 lib/src/google-map

polymer#0.5.5 lib/src/polymer
...
Polymer.dart bundles version 0.5.1 of webcomponentsjs so this could cause problems. Without re-running the apigen command, I try to load my application. Pub generates it OK, but it does not work in the browser and I see similar error messages in the console as I saw last night:
Uncaught HierarchyRequestError: Failed to execute 'appendChild' on 'Node': Nodes of type 'HTML' may not be inserted inside nodes of type '#document'.
Hopefully custom_element_apigen is smart enough to resolve this. I re-run the generate command:
$ pub run custom_element_apigen:update apigen.yaml
Done
After restarting pub serve I find that the application is working again. Yay!

Forcing webcomponentsjs dependencies would have been a significant pain had that not worked—especially if it prevented the use of newer JavaScript custom elements in Polymer.dart. Thankfully the custom_element_apigen package has me covered.

Happily, this worked out much better for me than my last attempt at working with custom_element_apigen. I still need to experiment with using the generated elements in other elements (material for tomorrow). But this is promising. It would be nice if the generator were a little more helpful about the files that need to be generated, but this was not too bad.

There is an outside chance that I may even remember custom_element_apigen in 6 months!


Day #9

3 comments:

  1. "There is an outside chance that I may even remember custom_element_apigen in 6 months!"

    I doubt it. I have enough trouble remembering things I don't use all the time myself. So unless you end up using custom_element_apigen a lot in the next few months, you'll probably forget, like all good developers :-)

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete
  3. Great post!
    I would like a Paper Datatable in Dart. We have an nice implementation in JavaScript here: https://github.com/David-Mulder/paper-datatable.
    I did not have success with Custom Element Apigen for this component.
    If possible, it would be interesting to see a post on this component Paper Datatable. I think that would help many people!

    ReplyDelete