Saturday, March 29, 2014

A Dart Pub Transform to Strip Polymer Elements


Today I start on a build script for a Polymer.dart element.

There is already a nice Dart build transformer that comes with the Polymer.dart package. It does a reasonable job of building a Polymer element into a web page for deployment to the modern web. I want to take it a step further. I would like my script to build the Polymer element for deployment to any page.

I have manually done just that over the past two days, using mostly command-line tools. I could probably work those command-lines into a Bash script without much difficulty, but I think it more useful to rework that effort in the form of another Dart transformer. This Dart transformer will (at least initially) use the results of the Polymer.dart transform, and transform them into a single script for deployment.

I am looking at this as a multi-day effort, so tonight I will extract the Polymer templates that the Polymer.dart transformer leaves in the web page that it builds. As I found two nights ago, it is possible to place them in a JavaScript file to be dynamically added to the DOM for ultimate use by the Polymer.dart platform.

For that, I need to write my own custom transformer. There is a nice article on the subject from the fine Dart folks, which saves me some work. I would like the transformer settings to follow the same convention as the original transformer—at least to start. So my pubspec.yaml becomes:
name: deployment_experiment
dependencies:
  polymer: any
dev_dependencies:
  scheduled_test: any
transformers:
- polymer:
    entry_points: web/index.html
- deployment_experiment:
    entry_points: web/index.html
The bulk of the work in this transformer, which I start in lib/transformer.dart, comes from the aforementioned article. This gives me a skeleton transformer of:
library single_file.transformer;

import 'dart:async';
import 'package:barback/barback.dart';

class SingleJsPolymerTransformer extends Transformer {
  String entry_points;

  SingleJsPolymerTransformer(this.entry_points);

  SingleJsPolymerTransformer.fromList(List a): this(a[0]);

  SingleJsPolymerTransformer.asPlugin(BarbackSettings settings)
    : this.fromList(_parseSettings(settings));

  Future<bool> isPrimary(Asset input) {
    if (entry_points == input.id.path) return new Future.value(true);
    return new Future.value(false);
  }

  Future apply(Transform transform) {
    // Need to do stuff here....
  }
}

List<String> _parseSettings(BarbackSettings settings) {
  var args = settings.configuration;
  return [ args['entry_points'] ];
}
Starting from that skeleton, I need to build the transform that will take the normal Polymer transform and convert everything into a single file. First up, my transformer needs to remove the <polymer-template> tags from the Polymer transformation step:
<!DOCTYPE html><html lang="en"><head>
<script src="/scripts/shadow_dom.min.js"></script>
<script src="/scripts/custom-elements.min.js"></script>
<script src="/scripts/interop.js"></script>
<script src="/scripts/index.html_bootstrap.dart.js"></script>
</head>
<body>
  <polymer-element name="x-pizza-toppings">
    <template><!-- ... --></template>
  </polymer-element>
  <polymer-element name="x-pizza">
    <template><!-- ... --></template>
  </polymer-element>
  <div class="container">
    <h1>Ye Olde Dart Pizza Shoppe</h1>
    <x-pizza></x-pizza>
  </div>
</body></html>
In pub transfomers, that means that I need to search and replace for text in a string, then update the asset in the transform. I accomplish that with:
class SingleJsPolymerTransformer extends Transformer {
  // ...
  Future apply(Transform transform) {
    var input = transform.primaryInput;
    var re = new RegExp(
      r'<polymer-element[\s\S]+/polymer-element>',
      multiLine: true
    );

    return transform.
      readInputAsString(input.id).
      then((html){
        var fixed = html.replaceAllMapped(
          re,
          (m) {
            return '';
          }
        );
        transform.addOutput(new Asset.fromString(input.id, fixed));
      });
  }
}
The hard part comes next. In addition to simply removing this text from an existing asset, I need to create a new asset (which requires a new, unique asset ID) and convert the <polymer-element> text into something that can be dynamically added via a JavaScript resource. Something like this ought to do the trick:
class SingleJsPolymerTransformer extends Transformer {
  // ...
  Future apply(Transform transform) {
    var input = transform.primaryInput;
    var re = new RegExp(
      r'<polymer-element[\s\S]+/polymer-element>',
      multiLine: true
    );

    return transform.
      readInputAsString(input.id).
      then((html){
        var polymer_template;
        var fixed = html.replaceAllMapped(
          re,
          (m) {
            polymer_template = m.group(0);
            return '';
          }
        );
        transform.addOutput(new Asset.fromString(input.id, fixed));

        var templateId = new AssetId(
          'deployment_experiment',
          'web/scripts/polymer_template.js'
        );
        transform.addOutput(
          new Asset.fromString(
            templateId,
            '''
var _html = '' +
${polymer_template.split('\n').map((s)=> "'" + s + "' +").join('\n')}
'';

window.addEventListener('DOMContentLoaded', function(){
  var container = document.createElement('div');
  container.innerHTML = _html;
  document.body.appendChild(container);
});
          '''
          )
        );
      });
  }
}
Inside the replaceAll, I squirrel away the <polymer-element> text in the polymer_template variable so that it can be used later. Then, I create a new web/scripts/polymer_template.js asset. Finally, I convert the <polymer-elements> into a bunch of concatenated JavaScript strings so that this will still work on the modern web (this is more or less what I did manually yesterday).

And, happily, this seems to work. If I use this generated JavaScript file in my build-generated code, then I still have a working Polymer. That seems a good stopping point for today. Up tomorrow, I need to combine this generated JavaScript code with the other JavaScript code that is normally generated in separate files by the normal Polymer transformer. My code is also a little messy, so some cleanup is in order. Hopefully this will prove relatively easy, but there is only one way to really know. Which I will find out tomorrow!


Day #18

No comments:

Post a Comment