In this codelab, you’ll learn how to get started writing Polymer apps using the Polymer Starter Kit and write your JS code using the ES2015 syntax - formally known as ES6 - while keeping your app backward compatible with all Polymer-supported browsers.

What you’ll learn

What you’ll need

How will you use this tutorial?

Read it through onlyRead it and complete the exercises

What is your current level of experience working with Polymer?

NoviceIntermediateProficient

What is your current level of experience with ES2015?

NoviceIntermediateProficient

You can do this codelab using any text editor you like. You'll also need to be able to run commands on the command line. If you don't have another editor handy, you can download the Chrome Dev Editor.

Download and install the Polymer Starter Kit

Download Polymer Starter Kit v1.3.0 - full edition

Download Polymer Starter Kit v1.3.0 using the button above. You’re getting the full version (there is also a ‘light’ version) which contains a build pipeline and additional features. Now unzip the archive.

You need to run bower install from the command line to install a list of dependencies (including the Polymer core library) into the app/bower_components/ folder.

You also need to run npm install from the command line to install the build tools and dependencies such as Gulp and its plugins into the node_modules/ folder.

Your folder structure should now look like this:

polymer-starter-kit-1.3.0/

  app/  <!-- your app's source files (html/js/images) -->
  app/bower_components/ <!-- Installed dependencies from Bower -->
  bower.json  <!-- Bower metadata files used for managing deps -->
  gulpfile.js  <!-- Contains all your build file rules -->
  node_modules/ <!-- Installed dependencies from npm -->
  package.json  <!-- npm metadata files used for managing deps -->
  ...


Polymer Starter Kit comes with an app’s scaffold as a starting point. The app’s source files are located under app/. We’ll see more of the feature that are ready to use in the scaffold app in the following sections.

Preview the app

We use Gulp as our build tool. Running npm install should have installed Gulp and all the plugins and tools listed in the package.json. Gulp is using a build file called gulpfile.js where you define all of your build tasks.

To preview the app run gulp serve. This will build the app and start serving a development version on http://localhost:5000

Any changes to your app’s files automatically trigger a partial build and a browser refresh of your webapp so you can see your changes right away.

Why a compile/build step?

It is not generally necessary to build fully client-side apps so you might be wondering why Polymer Starter Kit comes with a build pipeline. Adding a build step to your client apps can allow you to add important and useful features. Here is what happens when gulp is run:

These can help simplify your development and are best practices for production ready projects.

During this codelab we'll use ES2015 syntax such as classes and arrow functions. Although support for ES2015 is improving in modern browsers, many do not yet support the full set of features. To benefit from the awesomeness of the new ES2015 syntax while keeping backwards compatibility with Polymer's supported browsers, you'll need to transpile your JS code from ES2015 to ES5 using a tool called BabelJS.

Since we have a build pipeline already thanks to Polymer Starter Kit we’ll just have to add a transpile step.

Create a transpile gulp task

Install the gulp Babel, Sourcemap and Crisper plugins:

npm install --save-dev gulp-babel gulp-sourcemaps gulp-crisper babel-preset-es2015

Add the following gulp task in the gulpfile.js file after the vulcanize task:

gulpfile.js

// Transpile all JS to ES5.
gulp.task('js', function () {
 return gulp.src(['app/**/*.{js,html}', '!app/bower_components/**/*'])
   .pipe($.if('*.html', $.crisper({scriptInHead:false}))) // Extract JS from .html files
   .pipe($.sourcemaps.init())
   .pipe($.if('*.js', $.babel({
     presets: ['es2015']
   })))
   .pipe($.sourcemaps.write())
   .pipe(gulp.dest('.tmp/'))
   .pipe(gulp.dest('dist/'));
});

This task will transpile all JS files and inline JS inside HTML files and also generate sourcemaps. The resulting files are generated in the .tmp directory.

Make Vulcanize play nice with transpiled code

Update the vulcanize task to use the transpiled source files in the .tmp directory, instead of the app directory.

gulpfile.js

// Vulcanize granular configuration
gulp.task('vulcanize', function() {
  return gulp.src('.tmp/elements/elements.html') // look in .tmp dir!
    .pipe($.vulcanize({
      stripComments: true,
      inlineCss: true,
      inlineScripts: true
    }))
    .pipe(gulp.dest(dist('elements')))
    .pipe($.size({title: 'vulcanize'}));
});

For the vulcanize task to properly look up all of our dependencies, we’ll need to modify the copy task as well so that it places any potentially referenced components in the .tmp directory. The copy task already contains app and bower subtasks. Leave those as they are, and add a third subtask called tmp.

gulpfile.js

// Copy all files at the root level (app)
gulp.task('copy', function() {

  var app = ...

  var bower = ...

  // Add components to .tmp dir so they can get concatenated
  // when we vulcanize
  var tmp = gulp.src(['app/bower_components/**/*'])
    .pipe(gulp.dest('.tmp/bower_components'));

  return merge(app, bower, tmp)
    .pipe($.size({
      title: 'copy'
    }));

});

Integrating the transpile task

You’ll need to make sure the js gulp task is triggered by the common build tasks.

First, in the gulp serve task, make sure the js task is triggered initially and on HTML and JS files changes:

gulpfile.js

gulp.task('serve', ['styles', 'js'], function () { // Added 'js' here!

  ...

  gulp.watch(['app/**/*.html', '!app/bower_components/**/*.html'], ['js', reload]); // Added 'js' here!
  gulp.watch(['app/styles/**/*.css'], ['styles', reload]);
  gulp.watch(['app/scripts/**/*.js'], ['js', reload]); // Added 'js' here!
  gulp.watch(['app/images/**/*'], reload);
});

This will make sure the js task is triggered whenever you modify an HTML or JS file when running gulp serve so that your code gets transpiled and your app refreshed when developing.

Then, in the default task make sure js is run in parallel to images, fonts, and html tasks:

gulpfile.js

gulp.task('default', ['clean'], function (cb) {
  // Uncomment 'cache-config' if you are going to use service workers.
  runSequence(
    ['ensureFiles', 'copy', 'styles'],
    ['images', 'fonts', 'html', 'js'],
    'vulcanize', // 'cache-config',
    cb);
});

This ensures the js task is run when doing a fresh build of your app.

The html task takes care of getting the HTML, CSS and JS code ready for production by minifying and concatenating files. We need to make sure the transpiled version of the JS files are being picked up and not the original ES2015 files which won’t work on all browsers. To do this remove the app folder from the useref search path in the html task:

gulpfile.js

// Scan Your HTML For Assets & Optimize Them
gulp.task('html', function () {
  var assets = $.useref.assets({searchPath: ['.tmp', 'dist']}); // Removed 'app' here!

  ...

});

Checking everything works

Let’s have a quick look to see if everything seem to run smoothly: run gulp to start a full build.

Please make sure a .tmp folder has been created and contains at least these files under elements/my-list/ and scripts/:

polymer-starter-kit-1.3.0/
  .tmp/  <!-- The temporary build folder -->
    elements/  <!-- Folder containing custom Polymer elements -->
      my-list/  <!-- A sample custom Polymer element's folder -->
        my-list.html  <!-- Template of the custom 'my-list' element -->
        my-list.js  <!-- JS code separated from the HTML by Crisper -->
    ...
    scripts/  <!-- Folder containing non-Polymer-elements JS code -->
      app.js  <!-- A JS file -->
  ...

This shows us that our new js build task has been at work by separating the JavaScript code from the Polymer elements HTML files. If you take a look at my-list.js you should see a big block of commented code at the bottom that starts with sourceMappingURL. That’s the sourcemap, compiled into our element’s JavaScript.

That’s it! You should now be able to write ES2015 JavaScript in your app. Let’s go do that.

Now lets see how to create a custom Polymer element using the ES2015 syntax. Let’s do a simple Meme Generator and we’ll add this meme to the existing page:

Create your custom element

Create the following folder: app/elements/awesome-meme/

In this folder create a file named awesome-meme.html which will contain your custom element’s code.

You’ll need the meme’s background image. Save this image in the app/images/ folder as awesome.png.

To create the “awesome-meme” Polymer element we need to define a <dom-module> that contains a <style>, <template> and <script> tags. Add this to awesome-meme.html:

awesome-meme.html

<link rel="import" href="../../bower_components/polymer/polymer.html">

<dom-module id="awesome-meme">
  <template>
    <style>
      /* TODO: add CSS */
    </style>

    <!-- TODO: add the template's HTML content -->
  </template>

  <script>
    /* TODO: create the Polymer element's definition in ES2015 */
  </script>
</dom-module>

The Meme will simply be made of two configurable text blocks over a background image. Add this inside the <template> tag:

awesome-meme.html

<div class="top">{{top}}</div>
<div class="bottom">{{bottom}}</div>

The two text blocks will use absolute positioning.  Add this inside the <style> tag:

awesome-meme.html

:host {
  display: block;
  background-image: url("/images/awesome.png");
  width: 300px;
  height: 300px;
  position: relative;
}
.top, .bottom{
  position: absolute;
  font-size: 30px;
  font-weight: bold;
  width: 100%;
  text-align: center;
}
.top {
  top: 20px;
}
.bottom {
  bottom: 20px;
}

Next you’ll define the Polymer element using an ES2015 class definition. Ours is simple. We’ll define the top and bottom properties and capitalize the text of the Meme. Add this to the  <script> tag:

awesome-meme.html

class AwesomeMeme {
  beforeRegister() {
    this.is = 'awesome-meme';
    this.properties = {
      top: {
        type: String,
        value: ''
      },
      bottom: {
        type: String,
        value: ''
      }
    };
  }
  created() {}
  ready() {
    this.top = this.top.toUpperCase();
    this.bottom = this.bottom.toUpperCase();
  }
  attached() {}
  detached() {}
  attributeChanged() {}
}

Polymer(AwesomeMeme);

All the Polymer element’s callback functions (beforeRegister, created, ready, attached, detached, attributeChanged) are listed here for educational purposes. You don’t actually need to list empty callback functions so feel free to remove created() {}, attached() {}, detached() {} and attributeChanged() {}.

Using classes to define a Polymer elements will allow you to benefit from better readability and maintainability but also easily set up inheritance hierarchies.

Optionally you can now also upgrade the JavaScript code of the other two custom Polymer elements to ES2015 similarly. You will find them in:

Use your custom element in your app

Now you can use and configure your new Meme Polymer element in your app. First, you need to import the element in your main page. Add the following line in the app/elements/elements.html file:

elements.html


...

<link rel="import" href="../styles/app-theme.html">
<link rel="import" href="my-greeting/my-greeting.html">
<link rel="import" href="my-list/my-list.html">
<!-- Added the line below -->
<link rel="import" href="awesome-meme/awesome-meme.html">

You can now add your new Meme element in your app’s page. Let’s add it inside the second card of the page. Add the following one line to the app/index.html file:

index.html


...

<paper-material elevation="1">
  <p class="paper-font-body2">This is another card.</p>
  <!-- Added the line below -->
  <awesome-meme top="ES2015" bottom="is awesome!"></awesome-meme>
</paper-material>

...

Now run gulp serve to see the result:

Let’s have a look at some other JavaScript files that come with Polymer Starter Kit and “upgrade” them to ES2015.

Routing rules

Let’s have a look at the app/elements/routing.html file and let’s switch all these anonymous functions to arrow functions:

routing.html

<script src="../../bower_components/page/page.js"></script>
<script>
  window.addEventListener('WebComponentsReady', () => { // Arrow function!

    // We use Page.js for routing. This is a Micro
    // client-side router inspired by the Express router
    // More info: https://visionmedia.github.io/page.js/

    ...

    // Routes
    page('*', scrollToTop, closeDrawer, (ctx, next) => { // Arrow function!
      next();
    });

    page('/', () => { // Arrow function!
      app.route = 'home';
      app.scrollPageToTop();
    });

    page('/users', () => { // Arrow function!
      app.route = 'users';
      setFocus(app.route);
    });

    page('/users', () => { // Arrow function!
      app.route = 'users';
      app.scrollPageToTop();
    });

    page('/users/:name', data => { // Arrow function!
      app.route = 'user-info';
      app.params = data.params;
      app.scrollPageToTop();
    });

    page('/contact', () => { // Arrow function!
      app.route = 'contact';
      app.scrollPageToTop();
    });

    // 404
    page('*', () => { // Arrow function!
      app.$.toast.text = 'Can\'t find: ' + window.location.href  + '. Redirected you to Home Page';
      app.$.toast.show();
      page.redirect(app.baseUrl);
    });


    // add #! before urls
    page({
      hashbang: true
    });
  });
</script>

Arrow functions offer a quick and readable way to write anonymous functions. They also automatically bind the this value.

Test this new code by clicking on Users or Contact in the menu. The corresponding sub-pages should display:

App’s code

Now let’s have a look at the app/scripts/app.js file. In this file you can start by changing anonymous functions to arrow functions like we just did above.

Most of the time when you declare a var in JavaScript, you actually want let semantics which declares a block scope variable instead of a function scope variable. So let’s change all the var:

app.js


...

let app = document.querySelector('#app');

...

  let appName = document.querySelector('.app-name');
  let middleContainer = document.querySelector('.middle-container');
  let bottomContainer = document.querySelector('.bottom-container');
  let detail = e.detail;
  let heightDiff = detail.height - detail.condensedHeight;
  let yRatio = Math.min(1, detail.y / heightDiff);
  let maxMiddleScale = ...
  let scaleMiddle = ...
  let scaleBottom = 1 - yRatio;

You are now only using block scope variables. It didn’t really make a difference in this particular case because all the variables are declared at the top of a function (so block scope === function scope) but it’s good practice to get used to using let since it’s usually what you need and it makes for clearer, more robust code.

ES2015 comes with yet another awesome feature called template Strings which allow you to do string interpolation. Lets change the 3 string concatenations into something more elegant:

app.js


...

// Move/translate middleContainer
Polymer.Base.transform(`translate3d(0,${yRatio * 100}%,0)`, middleContainer);

// Scale bottomContainer and bottom sub title to nothing and back
Polymer.Base.transform(`scale(${scaleBottom}) translateZ(0)`, bottomContainer);

// Scale middleContainer appName
Polymer.Base.transform(`scale(${scaleMiddle}) translateZ(0)`, appName);

...

Now check that gulp serve is still running and go back to your browser to make sure the app still works as expected. For instance, check that the title shrinks when you scroll down since that’s one of the functions that app.js handles:

Congrats! You’re done! Feel free to compare what you have with: https://github.com/googlecodelabs/psk-es2015

You’ve written a custom Polymer element using the ES2015 and kept your app compatible with all Polymer supported browsers!

What you’ve learned

Learn More

There is more to learn on Polymer Starter Kit itself as it offers several other features configured out of the box like Service Worked support, Testing

Polymer

Other