npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

polymer-rename

v4.3.0

Published

Rename polymer template databinding expressions and event functions with closure-compiler

Downloads

450

Readme

polymer-rename

npm version

Updated for Polymer 2

Closure-compiler with ADVANCED optimizations, offers the powerful ability to rename properties. However, it can only safely be used on code bases which follow its required conventions. With the introduction of polymer templates, data binding expressions become external uses of code which the compiler must be made aware of or optimizations such as dead-code elimination and property renaming will break the template references. While it's possible to quote or export all of the properties referenced from polymer templates, that action significantly decreases both the final compression as well as the type checking ability of the compiler.

This project offers an alternative approach. Prior to compilation with Closure-compiler, the Polymer template html is parsed and any data binding expressions are extracted. These extracted expressions are then output in an alternative form as javascript. This extracted javascript is never intended to be actually executed, but it is provided to Closure-compiler during compilation. The compiler can then safely rename references and thus the need to export or quote properties used in data-binding is eliminated.

In addition, considerable type checking is enabled for data-binding expressions.

Original

<dom-module id="add-or-subtract">
  <template>
    <div><button on-tap="_addOne">Increase</button></div>
    <div><button on-tap="_subtractOne">Decrease</button></div>
  </template>
  <script>
    /**
     * @polymer
     * @customElement
     * @extends {Polymer.Element}
     */
    class AddOrSubtractElement extends Polymer.Element {
      static get is() { return 'add-or-subtract'; }
      static get properties() {
        return {
          value: {
            type: Number,
            notify: true
          }
        };
      }
      _addOne() { this.value++; }
      _subtractOne() { this.value--; }
    }
    customElements.define(AddOrSubtractElement.is, AddOrSubtractElement);
  </script>
</dom-module>
<dom-module id="foo-bar">
  <template>
    <template is="dom-repeat" items="[[numList]]" as="num">
      <add-or-subtract on-value-changed="{{_valueChanged}}" value="[[num]]"></add-or-subtract>
    </template>
  </template>
  <script>
    /**
     * @polymer
     * @customElement
     * @extends {Polymer.Element}
     */
    class FooBarElement extends Polymer.Element {
      static get is() { return 'foo-bar'; }
      static get properties() {
        return {
          numList: {
            type: Array,
            value: () => [1, 2, 3, 4]
          }
        };
      }
      _valueChanged() {}
    }
    customElements.define(FooBarElement.is, FooBarElement);
  </script>
</dom-module>

After Compilation/Renaming

<dom-module id="add-or-subtract">
  <template>
    <div><button on-tap="a">Increase</button></div>
    <div><button on-tap="b">Decrease</button></div>
  </template>
  <script>
    class A extends Polymer.Element {
      static get is() { return 'add-or-subtract'; }
      static get properties() {
        return {
          c: {
            type: Number,
            notify: true
          }
        };
      }
      a() { this.c++; }
      b() { this.c--; }
    }
    customElements.define(A.is, A);
  </script>
</dom-module>
<dom-module id="foo-bar">
  <template>
    <template is="dom-repeat" items="[[d]]" as="e">
      <add-or-subtract on-c-changed="{{f}}" value="[[e]]"></add-or-subtract>
    </template>
  </template>
  <script>
    class B extends Polymer.Element {
      static get is() { return 'foo-bar'; }
      static get properties() {
        return {
          d: {
            type: Array,
            value: () => [1, 2, 3, 4]
          }
        };
      }
      f() {}
    }
    customElements.define(B.is, B);
  </script>
</dom-module>

Using Polymer 2 Renaming

For Polymer 1, Closure-Compiler blocked renaming of any declared property. With Polymer 2, the compiler now uses the standard conventions: quoted properties are not renamed and other properties are. Declared properties with the reflectToAttribute or readOnly properties will never be renamed.

In addition, this project will rename attributes which map to properties of a custom element.

Usage

This project functions in two phases: pre and post closure-compiler compilation. Each phase has its own gulp plugin or can be used as native JS functions.

Pre-compilation: Extracting Data-binding Expressions

This first phase of the project parses polymer element templates and produces a javascript file to pass to closure-compiler.

The plugin makes use of the polymer-analyzer and requires both the HTML templates as well as any external JS source that contains element definitions.

Gulp

const gulp = require('gulp');
const polymerRename = require('polymer-rename');

gulp.task('extract-data-binding-expressions', function() {
  gulp.src('/src/components/**/*.html') // Usually this will be the bundled file - may also need to add .js files
      .pipe(polymerRename.extract({
        outputFilename: 'foo-bar.template.js'
      }))
      .pipe(gulp.dest('./build'));
});

Example Compilation

Closure-compiler's code-splitting flags allow the output to be divided into separate files. The compilation must reference the extern file included with this package.

In addition, the compiler can now warn about mismatched types, misspelled or missing property references and other checks.

let closureCompiler = require('google-closure-compiler');

gulp.task('compile-js', function() {
  gulp.src(['./src/js/app.js', './build/foo-bar.template.js'])
      .pipe(closureCompiler({
        compilation_level: 'ADVANCED',
        warning_level: 'VERBOSE',
        polymer_pass: true,
        module: [
          'app:1',
          'foo-bar.template:1:app'
        ],
        externs: [
          require.resolve('google-closure-compiler/contrib/externs/polymer-1.0.js'),
          require.resolve('polymer-rename/polymer-rename-externs.js')
        ]
      })
      .pipe(gulp.dest('./dist'));
});

Post-compilation: Find the Renamed Properties And Update the Template

After compilation, the compiler will have consistently renamed references to the properties. The generated javascript contains indexes into the original template which will now be replaced with their renamed versions.

Gulp

const polymerRename = require('polymer-rename');

gulp.task('update-html-template', function() {
  gulp.src('./src/components/foo-bar.html') // Usually this will be the bundled file
      .pipe(polymerRename.replace('./dist/foo-bar.template.js'))
      .pipe(gulp.dest('./dist/components'));
});

Element Type Names

polymer-rename obtains the type names of elements from polymer-analyzer. Type names for elements must be global.

Examples

Giving the following polymer template, the first phase of the project will extract the data-binding expressions and create a valid JS file:

<dom-module id="foo-bar" assetpath="/">
  <template>
    <div on-click="nameClicked">[[formatName(name)]]</div>
    <div>[[employer]]</div>
    <template is="dom-repeat" items="[[addresses]]">
      <div>[[item.street]]</div>
      <div>[[item.city]], [[item.state]] [[item.zip]]</div>
    </template>
  </template>
  <script>
  /**
   * @polymer
   * @customElement
   * @extends {Polymer.Element}
   */
  class FooBarElement extends Polymer.Element {
    static get is() { return "foo-bar"; }
    static get properties() {
      return {
        name: String,
        employer: String,
        
        /** @type {Array<{street: string, city: string, state: string, zip: string}>} */
        addresses: Array
      };
    }
    
    /**
     * @param {?string} a
     * @return {string}
     */
    formatName(a) { return a || ''; },
    
    /** @param {!Event=} evt */
    nameClicked(evt) {
      console.log(this.name);
    }
  }
  customElements.define(FooBarElement.is, FooBarElement);
  </script>
</dom-module>

Generated JavaScript from the extracted data-bound properties:

(/** @this {FooBarElement} */ function() {
  polymerRename.eventListener(72, 83, this.nameClicked);
  polymerRename.identifier(98, 102, this.name);
  this.formatName(this.name);
  polymerRename.method(87, 97, this.formatName);
  polymerRename.identifier(123, 131, this.employer);
  polymerRename.identifier(179, 188, this.addresses);
  for (let index = 0; index < this.addresses.length; index++) {
    let item = this.addresses[index];
    polymerRename.identifier(206, 217, item.street, item, 'item');
    polymerRename.identifier(239, 248, item.city, item, 'item');
    polymerRename.identifier(254, 264, item.state, item, 'item');
    polymerRename.identifier(269, 277, item.zip, item, 'item');
  }
}).call(/** @type {FooBarElement} */ (document.createElement("foo-bar")))

Each of the special function calls is defined as an extern. Each call takes a pair of indexes where the expression resides in the original file. After compilation, these indexes are used to replace the original expression with the now renamed properties and methods.

The compiler consumes the JS file and outputs the renamed expressions:

(function() {
  polymerRename.eventListener(72, 83, this.a);
  polymerRename.identifier(98, 102, this.b);
  this.c(this.b);
  polymerRename.method(87, 97, this.c);
  polymerRename.identifier(123, 131, this.d);
  polymerRename.identifier(179, 188, this.e);
  for (let a = 0; a < this.e.length; a++) {
    let b = this.e[a];
    polymerRename.identifier(206, 217, b.f, b, 'item');
    polymerRename.identifier(239, 248, b.g, b, 'item');
    polymerRename.identifier(254, 264, b.h, b, 'item');
    polymerRename.identifier(269, 277, b.i, b, 'item');
  }
}).call(document.createElement("foo-bar"))

The provided indexes are now used to update the original Polymer template.

Supporting String Based Polymer Features

Several functions and options in Polymer require providing property names as strings. This is problematic as Closure-Compiler does not rename strings. This would include the following features:

To work around such limitations, Closure-Compiler recognizes two special functions defined in Closure-Library. If your project does not utilize Closure-Library, you can simply copy the definitions for these two functions to your code base. As long as they are named the same, the compiler will recognize them.

Property Reflection with goog.reflect.objectProperty

goog.reflect.objectProperty returns a renamed string for an object instance. It's particularly helpful when calling notifyPath, notifySplices or set.

this.notifyPath(goog.reflect.objectProperty('foo', this), this.foo);

Property Reflection with goog.reflect.object

goog.reflect.object renames the keys of an object literal consistently with a provided constructor. It's useful when an instance of the object is not available.

// If using closure-library, this function is goog.object.transpose
function swapKeysAndValues(obj) {
  let swappedObj = {};
  Object.keys(obj).map(key => {
    swappedObj[obj[key]] = key;
  });
  return swappedObj;
}

var myCustomElementProps = swapKeysAndValues(
  goog.reflect.object(MyCustomElement, {
    _fooChanged: '_fooChanged'
  })
);

var MyCustomElement = Polymer({
  is: 'my-custom',
  properties: {
    foo: {
      type: Boolean,
      observer: myCustomElementProps['_fooChanged']
    }
  },
  _fooChanged: function(newValue, oldValue) {}
});

Limitations of This Project

Properties which are quoted are no longer renamed. However sub-paths are not analyzed. If the a property subpath should be blocked from renaming, use extern types to ensure that the compiler will not rename the paths.