Como criar apps da Web com o Yeoman e a Polymer

Capacite seus apps da Web com ferramentas modernas

Addy Osmani
Addy Osmani

Introdução

Allo! Qualquer pessoa que cria um aplicativo da Web sabe como é importante manter a produtividade. É um desafio quando você precisa se preocupar com tarefas tediosas, como encontrar o boilerplate certo, configurar um fluxo de trabalho de desenvolvimento e teste e minimizar e compactar todas as suas fontes.

Felizmente, as ferramentas de front-end modernas podem ajudar a automatizar grande parte disso, deixando você se concentrar em criar um app básico. Este artigo mostra como usar o Yeoman, um fluxo de trabalho de ferramentas para apps da Web que simplifica a criação de aplicativos usando o Polymer, uma biblioteca de polyfills e recursos para desenvolver apps usando componentes da Web.

Yeoman

Conheça Yo, Grunt e Bower

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

  • Você é uma ferramenta de scaffolding que oferece um ecossistema de scaffolds específicos do 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 o projeto, graças à ajuda das tarefas selecionadas pela equipe da Yeoman e do grunt-contrib.
  • O bower é usado no gerenciamento de dependências para que você não precise mais fazer o download e gerenciar os scripts manualmente.

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

É possível instalar geradores a partir de Módulos de pacote de nó (NPM, na sigla em inglês). Agora há mais de 220 geradores disponíveis, muitos deles 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, vá para o terminal mais próximo e execute:

$ npm install -g yo

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

Instalação de Yeoman

Gerador de polímero

Como mencionei anteriormente, o Polymer é uma biblioteca de polyfills e açúcar que permite o uso de componentes da Web em navegadores modernos. O projeto permite que os desenvolvedores criem aplicativos usando a plataforma do futuro e informem ao W3C os locais em que as especificações em trânsito podem ser ainda mais aprimoradas.

Página inicial do gerador de polímeros

O generator-polymer é um novo gerador que ajuda você a gerenciar apps Polymer com o Yeoman. Assim, é possível criar e personalizar facilmente elementos Polymer (personalizados) por meio da linha de comando e importá-los usando importações em HTML. Isso economiza tempo escrevendo o código boilerplate para você.

Em seguida, instale o gerador do Polymer executando:

$ npm install generator-polymer -g

Pronto. Agora seu app tem superpoderes do componente Web.

Nosso gerador recém-instalado tem alguns bits específicos que você vai poder acessar:

  • O polymer:element é usado para a base de novos elementos individuais do Polymer. Por exemplo: yo polymer:element carousel
  • O polymer:app é usado para scaffolding seu index.html inicial, um Gruntfile.js que contém a configuração do tempo de compilação para seu projeto, bem como tarefas do Grunt e uma estrutura de pastas recomendada para o projeto. Isso também lhe dará 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 personalizados da plataforma Polymer e nosso novo gerador.

App Polymer

Para começar, acesse o terminal, crie um novo diretório e use cd para dentro dele usando mkdir my-new-project && cd $_. Agora, inicie o app Polymer executando:

$ yo polymer
Criação de apps no Polymer

Ele obtém a versão mais recente do Polymer do Bower e cria scaffolds em um index.html, estrutura de diretório e tarefas do Grunt para seu fluxo de trabalho. Por que não pegar um café enquanto aguardamos o app terminar de ficar pronto?

Agora podemos executar grunt server para visualizar a aparência do app:

Servidor Grunt

O servidor suporta o LiveReload, o que significa que você pode abrir um editor de texto, editar um elemento personalizado e o navegador recarregará ao salvar. Isso fornece uma boa visualização em tempo real do estado atual do aplicativo.

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

$ yo polymer:element post
Criar elemento de postagem

Yeoman faz algumas perguntas, como se gostaríamos de incluir um construtor ou usar uma importação HTML para incluir o elemento post em index.html. Por enquanto, use Não para as duas primeiras opções e deixe a terceira opção 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 chamado 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:

Trabalhar com uma fonte real de dados

Nosso blog precisará de um local para escrever e ler novas postagens. Para demonstrar o trabalho com um serviço de dados real, usaremos a Google Apps Sheets API. Isso nos permite ler facilmente o conteúdo de qualquer planilha criada com o Google Docs.

Vamos fazer essa configuração:

  1. No navegador (para estas etapas, é recomendável usar o Chrome) abra esta planilha do Documentos Google. Ele contém exemplos de dados de postagem nos seguintes campos:

    • ID
    • Título
    • Autor
    • Conteúdo
    • Data
    • Palavras-chave
    • E-mail (do autor)
    • Slug (para o URL do 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 novamente o menu Arquivo e selecione Publicar na Web.

  4. Clique em Começar a publicar.

  5. Em Gerar um link para os dados publicados, na última caixa de texto, copie a parte key do URL fornecido. Ela tem o seguinte formato: https://docs.google.com/spreadsheet/ccc?key=0AhcraNy3sgspdDhuQ2pvN21JVW9NeVA0M1h4eGo3RGc#gid=0

  6. Cole a chave no seguinte URL em que está escrito your-key-goes-here: https://spreadsheets.google.com/feeds/list/your-key-goes-here/od6/public/values?alt=json-in-script&callback=. Veja um exemplo com a chave acima em: https://spreadsheets.google.com/feeds/list/0AhcraNy3sgspdDhuQ2pvN21JVW9NeVA0M1h4eGo3RGc/od6/public/values?alt=json-in-script

  7. Você pode colar o URL no seu navegador e navegar até ele para visualizar a versão JSON do conteúdo do seu blog. Anote o URL e revise o formato desses dados, porque será necessário iterá-los para exibi-los na tela mais tarde.

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

A API das Planilhas do Google gera cada um dos campos da 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 iteramos cada "linha" na saída JSON, faremos referência a esses campos para recuperar os valores relevantes para cada postagem.

Agora você pode editar o elemento de postagem recém-criado para bind partes de marcação aos dados da planilha. Para fazer isso, introduzimos o atributo post, que vai ser usado para o título, o autor, o conteúdo e outros campos da postagem criados anteriormente. O atributo selected, que será preenchido mais tarde, é usado para mostrar uma postagem apenas se um usuário navegar até o slug correto para ela.

<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 contenha 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, importamos o blog para index.html usando importações HTML para que ele apareça na página. No terceiro comando, especificamos post.html como o elemento que queremos incluir.

Como antes, um novo arquivo de elemento é criado (blog.html) e adicionado a /elementos. Desta vez, importando post.html e incluindo <post-element> dentro da tag de 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 para o elemento de blog ser importado usando importações HTML (uma forma 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>

Ótimo!

Como adicionar dependências usando o Bower

Agora, vamos editar nosso elemento para usar o utilitário Polymer JSONP (em inglês) para ler em posts.json. Você pode conseguir o adaptador clonando o repositório ou instalando polymer-elements pelo Bower executando bower install polymer-elements.

Dependências do Bower

Assim que tiver o utilitário, você precisará incluí-lo como uma importação no seu elemento blog.html com:

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

Em seguida, inclua a tag e forneça 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 em vigor, agora podemos adicionar modelos para iterar em nossa planilha depois de lê-la. A primeira gera um sumário, com um título vinculado para uma postagem que aponta para o slug para ela.

<!-- 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 em 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 que está sendo usado no modelo cria e mantém uma instância com [[bindings ]] para cada elemento na coleção de matriz das postagens, quando ele é fornecido.

App Polymer

Agora, para preencher o [[route]] atual, vamos usar uma biblioteca chamada Flatiron Director, que se vincula a [[route]] sempre que o hash do URL mudar.

Felizmente, há um elemento Polymer (parte do pacote more-elementos) que podemos usar para ele. Depois de copiar para o diretório /elementos, podemos fazer referência a ele com <flatiron-director route="[[route]]" autoHash></flatiron-director>, especificando route como a propriedade que queremos vincular e dizer a ele para ler automaticamente o valor de qualquer mudança 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 do JSON e usa dois elementos da Polymer integrados ao Yeoman.

Como trabalhar com elementos de terceiros

O ecossistema de elementos ao redor do Web Components vem crescendo ultimamente, com sites de galeria de componentes, como customelements.io, começando a aparecer. Analisando os elementos criados pela comunidade, encontrei um para buscar perfis gravatar e também podemos selecioná-lo e adicioná-lo ao nosso blog.

Página inicial dos elementos personalizados

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

<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 oferece:

App Polymer com elementos personalizados

Lindo!

Em um período relativamente curto, criamos um aplicativo simples composto por vários componentes da Web, sem que você precise se preocupar em escrever código boilerplate, fazer o download manual de dependências ou configurar um servidor local ou um fluxo de trabalho de compilação.

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 compilação (definidas em um Gruntfile) 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 inspeção, teste e criação:

grunt.registerTask('default', [

    'jshint',

    'test',

    'build'

]);

A tarefa jshint acima verificará suas preferências no arquivo .jshintrc e a executará em todos os arquivos JavaScript do projeto. Para ver todas as opções com o JSHint, consulte os documentos.

A tarefa test é mais ou menos assim e pode criar e disponibilizar seu app para o framework de teste recomendado, o Mocha. Ele também executará seus testes:

grunt.registerTask('test', [

    'clean:server',

    'createDefaultTemplate',

    'jst',

    'compass',

    'connect:test',

    'mocha'

]);

Como nosso app nesse caso é bastante simples, vamos deixar a criação de testes para você como um exercício separado. Precisamos que o processo de build processe outras coisas, então 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. Uma versão do app pronta para produção vai ser criada, pronta para o envio. Vamos testar.

Criação do grunt

Pronto.

Se você não souber o que fazer, acesse uma versão pré-criada do polymer-blog https://github.com/addyosmani/polymer-blog (em inglês).

O que mais temos na loja?

Os componentes da Web ainda estão em um estado de evolução e, por isso, as ferramentas que os cercam.

No momento, estamos analisando como concatenar as importações de HTML para melhorar o desempenho de carregamento com 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.

Informaremos você assim que tivermos respostas melhores para essas perguntas, mas há muitos momentos interessantes pela frente.

Como instalar o Polymer de forma independente com o Bower

Se você preferir um início mais leve do Polymer, pode instalá-lo de forma independente diretamente do Bower, executando:

bower install polymer

que o adicionará ao diretório bower_components. Depois, você pode referenciá-lo manualmente no índice do seu aplicativo e agitar o futuro.

O que você acha?

Agora você sabe como estruturar um app do Polymer usando componentes da Web com o Yeoman. Se você tiver algum feedback sobre o gerador, deixe-nos um comentário, registre um bug ou poste no Issue Tracker do Yeoman. Gostaríamos de saber se há algo mais que você gostaria que o gerador fosse melhor, já que é apenas por seu uso e feedback que podemos melhorar :)