Como criar apps da Web com o Yeoman e a Polymer

Crie um esqueleto para seus apps da Web com ferramentas modernas

Addy Osmani
Addy Osmani

Introdução

Allo’ Allo’. Qualquer pessoa que esteja escrevendo um app da Web sabe como é importante manter a produtividade. É um desafio ter que se preocupar com tarefas tediosas, como encontrar o modelo certo, configurar um fluxo de trabalho de desenvolvimento e teste e minimizar e compactar todas as suas fontes.

Felizmente, as ferramentas modernas de front-end podem ajudar a automatizar grande parte disso, permitindo que você se concentre em criar um app incrível. Este artigo mostra como usar o Yeoman, um fluxo de trabalho de ferramentas para apps da Web que simplifica a criação de apps usando o Polymer, uma biblioteca de polyfills e açúcar para desenvolver apps usando os Web Components.

Yeoman

Conheça Yo, Grunt e Bower

Yeoman é um homem de chapéu com três ferramentas para melhorar sua produtividade:

  • yo é uma ferramenta de scaffolding que oferece um ecossistema de scaffoldings específicos de framework, chamados de geradores, que podem ser usados para realizar algumas das tarefas tediosas que mencionei anteriormente.
  • O grunt é usado para criar, visualizar e testar seu projeto, graças à ajuda de tarefas selecionadas pela equipe do Yeoman e do grunt-contrib.
  • O bower é usado para gerenciamento de dependências, para que você não precise mais fazer o download e gerenciar seus scripts manualmente.

Com apenas um ou dois comandos, o Yeoman pode escrever código boilerplate para seu app (ou peças individuais, como modelos), compilar seu Sass, minimizar e concatenar CSS, JS, HTML e imagens e iniciar um servidor da Web simples no diretório atual. Ele também pode executar testes de unidade e muito mais.

É possível instalar geradores de módulos empacotados do Node.js (npm), e há mais de 220 geradores disponíveis, muitos dos quais foram criados pela comunidade de código aberto. Geradores conhecidos incluem generator-angular, generator-backbone e generator-ember.

Página inicial do Yeoman

Com uma versão recente do Node.js instalada, acesse o terminal mais próximo e execute:

$ npm install -g yo

Pronto! Agora você tem o Yo, o Grunt e o Bower e pode executá-los diretamente na linha de comando. Esta é a saída da execução de yo:

Instalação do Yeoman

Gerador de polímero

Como mencionei anteriormente, o Polymer é uma biblioteca de polyfills e açúcar que permite o uso de Web Components em navegadores modernos. O projeto permite que os desenvolvedores criem apps usando a plataforma do futuro e informem o W3C sobre os lugares em que as especificações em voo podem ser melhoradas.

Página inicial do gerador de Polymer

generator-polymer é um novo gerador que ajuda a criar um esqueleto de apps Polymer usando o Yeoman, permitindo que você crie e personalize facilmente elementos Polymer (personalizados) pela linha de comando e os importe usando importações HTML. Isso economiza tempo, porque o código boilerplate é escrito para você.

Em seguida, instale o gerador do Polymer executando:

$ npm install generator-polymer -g

É isso. Agora seu app tem superpoderes de componentes da Web.

Nosso gerador recém-instalado tem alguns bits específicos aos quais você terá acesso:

  • O polymer:element é usado para criar novos elementos individuais do Polymer. Por exemplo: yo polymer:element carousel
  • O polymer:app é usado para criar o index.html inicial, um Gruntfile.js que contém a configuração de build do projeto, além de tarefas do Grunt e uma estrutura de pastas recomendada para o projeto. Ele também oferece a opção de usar o Sass Bootstrap para os estilos do seu projeto.

Vamos criar um app Polymer

Vamos criar um blog simples usando alguns elementos Polymer personalizados e nosso novo gerador.

App Polymer

Para começar, acesse o terminal, crie um novo diretório e use o comando cd nele usando mkdir my-new-project && cd $_. Agora você pode iniciar seu app Polymer executando:

$ yo polymer
Desenvolvimento de apps Polymer

Isso vai buscar a versão mais recente do Polymer no Bower e criar um index.html, uma estrutura de diretório e tarefas do Grunt para seu fluxo de trabalho. Por que não tomar um café enquanto esperamos o app ficar pronto?

Agora podemos executar grunt server para conferir como o app vai ficar:

Servidor Grunt

O servidor oferece suporte ao LiveReload, o que significa que você pode ativar um editor de texto, editar um elemento personalizado e o navegador será recarregado ao salvar. Isso oferece uma boa visualização em tempo real do estado atual do app.

Em seguida, vamos criar um novo elemento Polymer para representar uma postagem do blog.

$ yo polymer:element post
Criar elemento de postagem

O Yeoman faz algumas perguntas, como se queremos incluir um construtor ou usar uma importação de HTML para incluir o elemento de postagem em index.html. Vamos dizer "Não" às duas primeiras opções por enquanto e deixar a terceira em branco.

$ yo polymer:element post

[?] Would you like to include constructor=''? No

[?] Import to your index.html using HTML imports? No

[?] Import other elements into this one? (e.g 'another_element.html' or leave blank)

    create app/elements/post.html

Isso cria um novo elemento Polymer no diretório /elements com o nome post.html:

<polymer-element name="post-element"  attributes="">

    <template>

    <style>
        @host { :scope {display: block;} }
    </style>

    <span>I'm <b>post-element</b>. This is my Shadow DOM.</span>

    </template>

    <script>

    Polymer('post-element', {

        //applyAuthorStyles: true,

        //resetStyleInheritance: true,

        created: function() { },

        enteredView: function() { },

        leftView: function() { },

        attributeChanged: function(attrName, oldVal, newVal) { }

    });

    </script>

</polymer-element>

Ele contém:

Como trabalhar com uma fonte real de dados

Nosso blog vai precisar de um lugar para escrever e ler novas postagens. Para demonstrar como trabalhar com um serviço de dados real, vamos usar a API Google Apps Sheets. Isso nos permite ler facilmente o conteúdo de qualquer planilha criada usando os Documentos Google.

Vamos configurar isso:

  1. No navegador (para estas etapas, recomendamos o Chrome), abra esta planilha do Documentos Google. Ele contém dados de postagens de exemplo nos seguintes campos:

    • ID
    • Título
    • Autor
    • Conteúdo
    • Data
    • Palavras-chave
    • E-mail (do autor)
    • Slug (para o URL de slug da sua postagem)
  2. Acesse o menu Arquivo e selecione Fazer uma cópia para criar sua própria cópia da planilha. Você pode editar o conteúdo quando quiser, adicionando ou removendo postagens.

  3. Acesse o menu Arquivo novamente e selecione Publicar na Web.

  4. Clique em Iniciar publicação.

  5. Em Obter um link para os dados publicados, na última caixa de texto, copie a parte chave do URL fornecido. Ele tem esta aparência: https://docs.google.com/spreadsheet/ccc?key=0AhcraNy3sgspdDhuQ2pvN21JVW9NeVA0M1h4eGo3RGc#gid=0

  6. Cole a chave no seguinte URL, onde está escrito sua-chave-vai-para-aqui: https://spreadsheets.google.com/feeds/list/your-key-goes-here/od6/public/values?alt=json-in-script&callback=. Um exemplo de uso da chave acima pode ser https://spreadsheets.google.com/feeds/list/0AhcraNy3sgspdDhuQ2pvN21JVW9NeVA0M1h4eGo3RGc/od6/public/values?alt=json-in-script.

  7. Você pode colar o URL no navegador e acessar para ver a versão JSON do conteúdo do blog. Anote o URL e analise o formato dos dados, porque você vai precisar iterar sobre eles para exibi-los na tela mais tarde.

A saída JSON no navegador pode parecer um pouco assustadora, mas não se preocupe. Só estamos interessados nos dados das suas postagens.

A API Google Spreadsheets gera cada um dos campos na planilha do blog com um prefixo especial post.gsx$. Por exemplo: post.gsx$title.$t, post.gsx$author.$t, post.gsx$content.$t e assim por diante. Quando iterarmos cada "linha" na saída JSON, faremos referência a esses campos para recuperar os valores relevantes de cada postagem.

Agora você pode editar o elemento de postagem recém-criado para vincular partes da marcação aos dados na planilha. Para isso, introduzimos um atributo post, que vai ler o título, o autor, o conteúdo e outros campos da postagem que criamos anteriormente. O atributo selected, que vamos preencher mais tarde, é usado para mostrar uma postagem somente se o usuário navegar até o slug correto dela.

<polymer-element name="post-element" attributes="post selected">

    <template>

    <style>
        @host { :scope {display: block;} }
    </style>

        <div class="col-lg-4">

            <template if="[[post.gsx$slug.$t === selected]]">

            <h2>
                <a href="#[[post.gsx$slug.$t]]">
                [[post.gsx$title.$t  ]]
                </a>
            </h2>

            <p>By [[post.gsx$author.$t]]</p>

            <p>[[post.gsx$content.$t]]</p>

            <p>Published on: [[post.gsx$date.$t]]</p>

            <small>Keywords: [[post.gsx$keywords.$t]]</small>

            </template>

        </div>

    </template>

    <script>

    Polymer('post-element', {

        created: function() { },

        enteredView: function() { },

        leftView: function() { },

        attributeChanged: function(attrName, oldVal, newVal) { }

    });

    </script>

</polymer-element>

Em seguida, vamos criar um elemento de blog que contém uma coleção de postagens e o layout do seu blog executando yo polymer:element blog.

$ yo polymer:element blog

[?] Would you like to include constructor=''? No

[?] Import to your index.html using HTML imports? Yes

[?] Import other elements into this one? (e.g 'another_element.html' or leave blank) post.html

    create app/elements/blog.html

Desta vez, vamos importar o blog para index.html usando importações de HTML da maneira que queremos que ele apareça na página. Para o terceiro comando, especificamos post.html como o elemento que queremos incluir.

Como antes, um novo arquivo de elemento é criado (blog.html) e adicionado a /elements. Desta vez, ele importa post.html e inclui <post-element> na tag do modelo:

<link rel="import" href="post.html">

<polymer-element name="blog-element"  attributes="">

    <template>

    <style>
        @host { :scope {display: block;} }
    </style>

    <span>I'm <b>blog-element</b>. This is my Shadow DOM.</span>

        <post-element></post-element>

    </template>

    <script>

    Polymer('blog-element', {

        //applyAuthorStyles: true,

        //resetStyleInheritance: true,

        created: function() { },

        enteredView: function() { },

        leftView: function() { },

        attributeChanged: function(attrName, oldVal, newVal) { }

    });

    </script>

</polymer-element>

Como pedimos que o elemento do blog fosse importado usando importações de HTML (uma maneira de incluir e reutilizar documentos HTML em outros documentos HTML) no nosso índice, também podemos verificar se ele foi adicionado corretamente ao documento <head>:

<!doctype html>
    <head>

        <meta charset="utf-8">

        <meta http-equiv="X-UA-Compatible" content="IE=edge">

        <title></title>

        <meta name="description" content="">

        <meta name="viewport" content="width=device-width">

        <link rel="stylesheet" href="styles/main.css">

        <!-- build:js scripts/vendor/modernizr.js -->

        <script src="bower_components/modernizr/modernizr.js"></script>

        <!-- endbuild -->

        <!-- Place your HTML imports here -->

        <link rel="import" href="elements/blog.html">

    </head>

    <body>

        <div class="container">

            <div class="hero-unit" style="width:90%">

                <blog-element></blog-element>

            </div>

        </div>

        <script>
        document.addEventListener('WebComponentsReady', function() {
            // Perform some behaviour
        });
        </script>

        <!-- build:js scripts/vendor.js -->

        <script src="bower_components/polymer/polymer.min.js"></script>

        <!-- endbuild -->

</body>

</html>

Fantástico.

Como adicionar dependências usando o Bower

Em seguida, vamos editar nosso elemento para usar o elemento utilitário Polymer JSONP para ler o posts.json. Você pode conseguir o adaptador clonando o repositório do git ou instalando o polymer-elements pelo Bower executando bower install polymer-elements.

Dependências do Bower

Depois de criar o utilitário, inclua-o como uma importação no elemento blog.html com:

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

Em seguida, inclua a tag e forneça o url à planilha de postagens do blog anterior, adicionando &callback= ao final:

<polymer-jsonp auto url="https://spreadsheets.google.com/feeds/list/your-key-value/od6/public/values?alt=json-in-script&callback=" response="[[posts]]"></polymer-jsonp>

Com isso, agora podemos adicionar modelos para iterar na planilha depois que ela for lida. A primeira gera uma tabela de conteúdo, com um título vinculado a uma postagem que aponta para o slug dela.

<!-- Table of contents -->

<ul>

    <template repeat="[[post in posts.feed.entry]]">

    <li><a href="#[[post.gsx$slug.$t]]">[[post.gsx$title.$t]]</a></li>

    </template>

</ul>

A segunda renderiza uma instância de post-element para cada entrada encontrada, transmitindo o conteúdo da postagem para ela. Estamos transmitindo um atributo post que representa o conteúdo da postagem de uma única linha da planilha e um atributo selected que será preenchido com uma rota.

<!-- Post content -->

<template repeat="[[post in posts.feed.entry]]">

    <post-element post="[[post]]" selected="[[route]]"></post-element>

</template>

O atributo repeat usado no nosso modelo cria e mantém uma instância com [[ bindings ]] para cada elemento na coleção de matrizes das nossas postagens, quando fornecido.

App Polymer

Para preencher o [[route]] atual, vamos usar uma biblioteca chamada Flatiron director, que se vincula a [[route]] sempre que o hash do URL muda.

Felizmente, há um elemento Polymer (parte do pacote more-elements) que podemos usar. Depois de copiar para o diretório /elements, podemos fazer referência a ele com <flatiron-director route="[[route]]" autoHash></flatiron-director>, especificando route como a propriedade a que queremos vincular e informando que ele deve ler automaticamente o valor de qualquer alteração de hash (autoHash).

Juntando tudo, temos:

    <link rel="import" href="post.html">

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

    <link rel="import" href="flatiron-director/flatiron-director.html">

    <polymer-element name="blog-element"  attributes="">

      <template>

        <style>
          @host { :scope {display: block;} }
        </style>

        <div class="row">

          <h1><a href="/#">My Polymer Blog</a></h1>

          <flatiron-director route="[[route]]" autoHash></flatiron-director>

          <h2>Posts</h2>

          <!-- Table of contents -->

          <ul>

            <template repeat="[[post in posts.feed.entry]]">

              <li><a href="#[[post.gsx$slug.$t]]">[[post.gsx$title.$t]]</a></li>

            </template>

          </ul>

          <!-- Post content -->

          <template repeat="[[post in posts.feed.entry]]">

            <post-element post="[[post]]" selected="[[route]]"></post-element>

          </template>

        </div>

        <polymer-jsonp auto url="https://spreadsheets.google.com/feeds/list/0AhcraNy3sgspdHVQUGd2M2Q0MEZnRms3c3dDQWQ3V1E/od6/public/values?alt=json-in-script&callback=" response="[[posts]]"></polymer-jsonp>

      </template>

      <script>

        Polymer('blog-element', {

          created: function() {},

          enteredView: function() { },

          leftView: function() { },

          attributeChanged: function(attrName, oldVal, newVal) { }

        });

      </script>

    </polymer-element>
App Polymer

Uhu! Agora temos um blog simples que lê dados JSON e usa dois elementos Polymer estruturados com Yeoman.

Como trabalhar com elementos de terceiros

O ecossistema de elementos em torno dos componentes da Web tem crescido ultimamente, com o surgimento de sites de galeria de componentes, como customelements.io. Analisando os elementos criados pela comunidade, encontrei um para buscar perfis do gravatar, e podemos extrair e adicionar esse recurso ao nosso site de blogs.

Página inicial de elementos personalizados

Copie as fontes do elemento gravatar para o diretório /elements, inclua-as por meio de importações de HTML em post.html e adicione ao modelo, transmitindo o campo de e-mail da nossa planilha como a origem do nome de usuário. Boom!

<link rel="import" href="gravatar-element/src/gravatar.html">

<polymer-element name="post-element" attributes="post selected">

    <template>

    <style>
        @host { :scope {display: block;} }
    </style>

        <div class="col-lg-4">

            <template if="[[post.gsx$slug.$t === selected]]">

            <h2><a href="#[[post.gsx$slug.$t]]">[[post.gsx$title.$t]]</a></h2>

            <p>By [[post.gsx$author.$t]]</p>

            <gravatar-element username="[[post.gsx$email.$t]]" size="100"></gravatar-element>

            <p>[[post.gsx$content.$t]]</p>

            <p>[[post.gsx$date.$t]]</p>

            <small>Keywords: [[post.gsx$keywords.$t]]</small>

            </template>

        </div>

    </template>

    <script>

    Polymer('post-element', {

        created: function() { },

        enteredView: function() { },

        leftView: function() { },

        attributeChanged: function(attrName, oldVal, newVal) { }

    });

    </script>

</polymer-element>

Vamos conferir o que isso nos dá:

App Polymer com elementos personalizados

Lindo!

Em um tempo relativamente curto, criamos um aplicativo simples composto por vários componentes da Web sem precisar se preocupar em escrever código clichê, fazer o download manual de dependências ou configurar um servidor local ou fluxo de trabalho de build.

Otimizar o aplicativo

O fluxo de trabalho do Yeoman inclui outro projeto de código aberto chamado Grunt, um executor de tarefas que pode executar várias tarefas específicas de build (definidas em um arquivo Grunt) para produzir uma versão otimizada do aplicativo. A execução de grunt por conta própria vai executar uma tarefa default que o gerador configurou para linting, testes e build:

grunt.registerTask('default', [

    'jshint',

    'test',

    'build'

]);

A tarefa jshint acima vai verificar o arquivo .jshintrc para saber suas preferências e depois vai ser executada em todos os arquivos JavaScript do projeto. Para conferir todas as opções do JSHint, consulte a documentação.

A tarefa test é parecida com esta e pode criar e oferecer seu app para o framework de teste que recomendamos, o Mocha. Ele também executa os testes para você:

grunt.registerTask('test', [

    'clean:server',

    'createDefaultTemplate',

    'jst',

    'compass',

    'connect:test',

    'mocha'

]);

Como nosso app é bastante simples, vamos deixar a criação de testes por sua conta como um exercício separado. Há algumas outras coisas que precisamos fazer com o processo de build. Vamos conferir o que a tarefa grunt build definida no Gruntfile.js vai fazer:

grunt.registerTask('build', [

    'clean:dist',    // Clears out your .tmp/ and dist/ folders

    'compass:dist',  // Compiles your Sassiness

    'useminPrepare', // Looks for <!-- special blocks --> in your HTML

    'imagemin',      // Optimizes your images!

    'htmlmin',       // Minifies your HTML files

    'concat',        // Task used to concatenate your JS and CSS

    'cssmin',        // Minifies your CSS files

    'uglify',        // Task used to minify your JS

    'copy',          // Copies files from .tmp/ and app/ into dist/

    'usemin'         // Updates the references in your HTML with the new files

]);

Execute grunt build e uma versão pronta para produção do app será criada e poderá ser enviada. Vamos testar.

Build do Grunt

Pronto.

Se você ficar preso, uma versão pré-criada do polymer-blog está disponível em https://github.com/addyosmani/polymer-blog.

O que mais temos?

Os componentes da Web ainda estão em evolução, assim como as ferramentas relacionadas a eles.

Atualmente, estamos analisando como concatenar as importações de HTML para melhorar a performance de carregamento usando projetos como o Vulcanize (uma ferramenta do projeto Polymer) e como o ecossistema de componentes pode funcionar com um gerenciador de pacotes como o Bower.

Vamos avisar você quando tivermos respostas melhores para essas perguntas, mas há muitas novidades a caminho.

Como instalar o Polymer de forma independente com o Bower

Se você preferir começar com uma versão mais leve do Polymer, instale-o diretamente no Bower executando o seguinte:

bower install polymer

que vai adicioná-lo ao diretório bower_components. Depois, você pode fazer referência a ele na sua indexação de aplicativos manualmente e arrasar no futuro.

O que você acha?

Agora você sabe como criar um aplicativo Polymer usando os Web Components com o Yeoman. Se você tiver feedback sobre o gerador, deixe um comentário ou informe um bug ou publique no rastreador de problemas do Yeoman. Queremos saber se há algo que você gostaria que o gerador fizesse melhor, porque só podemos melhorar com seu uso e feedback :)