Friday, April 4, 2014

Generalizing a Single Script Polymer.dart Solution


I have a Dart script to combine JavaScript generated from a Polymer.dart element. I would like to see if I can generalize it a bit.

I failed in my attempts to make a single JS file for Polymer.dart elements from a Dart Pub transformer. This means that I cannot generate a single JS file with a simple pub build. So instead, I aim for he next best thing: pub build; polymer-reduce. Hopefully this will mostly involve removing hard-coded values. But first...

I need to parse my project's pubspec.yaml:
name: deployment_experiment
dependencies:
  polymer: any
dev_dependencies:
  scheduled_test: any
transformers:
- polymer:
    entry_points: web/index.html
The polymer transformer, which is part of Polymer.dart, uses the entry_points setting to decide which pages to inspect for Polymer elements in need of transforming. Based on the name, I would guess that entry_points can be more than one page.

Anyhow, first things first, I need to be able to parse the YAML in pubspec.yaml, so I add the YAML library to… pubspec.yaml:
name: deployment_experiment
dependencies:
  polymer: any
dev_dependencies:
  scheduled_test: any
  yaml: any
transformers:
- polymer:
    entry_points: web/index.html
I also need to add this to last night's reduce.dart script:
#!/usr/bin/env dart

import 'dart:io';
import 'package:yaml/yaml.dart';

main() {
  var pubspecYaml = new File('pubspec.yaml').readAsStringSync();
  var pubspec = loadYaml(pubspecYaml);
  // ...
}
The transformers entry in pubspec.yaml is an ugly beast. It is a list of single key maps, which means that I need to filter the transformers list for entries where the single key is "polymer", then for each, I need to run last night's transform. It might be an ugly data structure, but Dart's built-in iterators take care of it nicely:
main() {
  var pubspecYaml = new File('pubspec.yaml').readAsStringSync();
  var pubspec = loadYaml(pubspecYaml);

  // var filename = 'web/index.html';
  pubspec['transformers'].
    where((t) => t.keys.length == 1 && t.keys.first == 'polymer').
    forEach((t){
      var filename = t['polymer']['entry_points'];
      new PolymerScriptReducer(filename).reduce();
    });
}
The PolymerScriptReducer class is a simple reworking of last night's procedural code into a procedural class that can run against multiple entries.

That turns out to be the most complex bit of the conversion. I rework last night's placement of reduced JavaScript and the HTML that uses it, which winds up simplifying things quite a bit. I had been creating the reduced files into a separate deploy sub-directory. Since this is meant to be part of pub build, I place the reduced files directly in the build directory generated by pub build.

The only other question that I would like answered is—what happens if multiple HTML files are listed as entry_points?
name: deployment_experiment
dependencies:
  polymer: any
dev_dependencies:
  scheduled_test: any
  yaml: any
transformers:
- polymer:
    entry_points:
      - web/index.html
      - web/index2.html
I make web/index2.html a copy of web/index.html and run pub build. That produces generated output for both HTML files:
➜  dart git:(master) ✗ tree -L 2 build
build
└── web
    ├── assets
    ├── index2.html
    ├── index2.html_bootstrap.dart.js
    ├── index2.html_bootstrap.dart.js.map
    ├── index2.html_bootstrap.dart.precompiled.js
    ├── index.html
    ├── index.html_bootstrap.dart.js
    ├── index.html_bootstrap.dart.js.map
    ├── index.html_bootstrap.dart.precompiled.js
    └── packages

3 directories, 8 files
To support either a single string entry or a list of strings, I need to inspect the runtime type of entry_points:
main() {
  var pubspecYaml = new File('pubspec.yaml').readAsStringSync();
  var pubspec = loadYaml(pubspecYaml);

  pubspec['transformers'].
    where((t) => t.keys.length == 1 && t.keys.first == 'polymer').
    forEach((t){
      var entry_points = t['polymer']['entry_points'];
      var filenames = entry_points.runtimeType == String ?
        [entry_points] : entry_points;

      filenames.
        forEach((filename) {
          new PolymerScriptReducer(filename).reduce();
        });
    });
}
And that does the trick. I doubt that anyone would need to generate multiple entry points like that—especially if the goal is a single JavaScript source file for a Polymer.dart element. Still, I cannot ignore the plural nature of the name. I will likely rework this bit further as a package in case others might have need of it—or if I need to reference it in Patterns in Polymer. Tomorrow.

Day #24

No comments:

Post a Comment