Crie seus apps da Web com ferramentas modernas
Introdução
Allo’ Allo’. Qualquer pessoa que escreva um app da Web sabe como é importante manter a produtividade. É um desafio quando você precisa se preocupar com tarefas tediosas, como encontrar o modelo certo, configurar um fluxo de trabalho de desenvolvimento e teste e minificar 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 escrever 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 recursos para desenvolver apps usando Web Components.
Conheça o Yo, o Grunt e o Bower
O Yeoman é um homem de chapéu com três ferramentas para melhorar sua produtividade:
- yo é uma ferramenta de scaffolding que oferece um ecossistema de scaffolds 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 pelo grunt-contrib.
- O bower é usado para o gerenciamento de dependências, para que você não precise mais baixar e gerenciar seus scripts manualmente.
Com apenas um ou dois comandos, o Yeoman pode escrever código boilerplate para seu app (ou partes individuais, como modelos), compilar seu Sass, minimizar e concatenar seu CSS, JS, HTML e imagens e iniciar um servidor da Web simples no seu diretório atual. Ele também pode executar testes de unidade e muito mais.
É possível instalar geradores de Node Packaged Modules (npm). Há mais de 220 geradores já disponíveis, muitos deles escritos pela comunidade de código aberto. Os geradores mais conhecidos incluem generator-angular, generator-backbone e generator-ember.
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. Confira o resultado da execução de yo:
Gerador de polímeros
Como mencionei anteriormente, o Polymer é uma biblioteca de polyfills e sugar que permite o uso de componentes da Web em navegadores mais recentes. O projeto permite que os desenvolvedores criem apps usando a plataforma do futuro e informem ao W3C onde as especificações em andamento podem ser melhoradas.
O generator-polymer é um novo gerador que ajuda você a criar 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 de HTML. Isso economiza tempo, já que 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.
O gerador que acabamos de instalar tem alguns recursos específicos que você pode acessar:
polymer:elementé usado para criar novos elementos individuais do Polymer. Por exemplo:yo polymer:element carouselpolymer:appé usado para estruturar o index.html inicial, um Gruntfile.js que contém a configuração de tempo 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 projeto.
Vamos criar um app do Polymer
Vamos criar um blog simples usando alguns elementos personalizados do Polymer e nosso novo gerador.
Para começar, acesse o terminal, crie um novo diretório e use o comando cd para acessá-lo usando mkdir my-new-project && cd $_. Agora você pode iniciar seu app Polymer executando:
$ yo polymer
Isso vai buscar a versão mais recente do Polymer no Bower e criar um arquivo index.html, uma estrutura de diretórios e tarefas do Grunt para seu fluxo de trabalho. Que tal tomar um café enquanto o app fica pronto?
Em seguida, execute grunt server para conferir como o app vai ficar:
O servidor é compatível com o LiveReload, o que significa que você pode abrir um editor de texto, editar um elemento personalizado e o navegador vai recarregar 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 de blog.
$ yo polymer:element post
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" para as 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 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:
- Código boilerplate para seu elemento personalizado, permitindo que você use um tipo de elemento DOM personalizado na sua página (por exemplo,
<post-element>) - Uma tag de modelo para modelos "nativos" do lado do cliente e estilos com escopo de amostra para encapsular os estilos do elemento.
- Registro de elementos boilerplate e eventos de ciclo de vida.
Como trabalhar com uma fonte de dados real
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 Spreadsheets. Isso permite que o conteúdo de qualquer planilha criada no Google Docs seja lido com facilidade.
Vamos configurar:
No seu navegador (recomendamos o Chrome), abra esta planilha do Google Docs. Ele contém dados de postagem de amostra nos seguintes campos:
- ID
- Título
- Autor
- Conteúdo
- Data
- Palavras-chave
- E-mail (do autor)
- Slug (para o URL do slug do post)
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 como quiser, adicionando ou removendo postagens.
Acesse o menu Arquivo novamente e selecione Publicar na Web.
Clique em começar a publicar.
Em Gerar um link para os dados publicados, na última caixa de texto, copie a parte da chave do URL fornecido. Ele tem esta aparência: https://docs.google.com/spreadsheet/ccc?key=0AhcraNy3sgspdDhuQ2pvN21JVW9NeVA0M1h4eGo3RGc#gid=0
Cole a chave no seguinte URL, onde está escrito your-key-goes-here: https://spreadsheets.google.com/feeds/list/your-key-goes-here/od6/public/values?alt=json-in-script&callback=. Um exemplo usando a chave acima seria https://spreadsheets.google.com/feeds/list/0AhcraNy3sgspdDhuQ2pvN21JVW9NeVA0M1h4eGo3RGc/od6/public/values?alt=json-in-script.
Você pode colar o URL no navegador e acessá-lo para conferir a versão JSON do conteúdo do seu blog. Anote o URL e analise o formato desses dados, porque você vai precisar iterar sobre eles para que sejam exibidos na tela mais tarde.
A saída JSON no navegador pode parecer um pouco assustadora, mas não se preocupe. Só queremos os 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. Ao iterar cada "linha" na saída JSON, vamos referenciar esses campos para receber os valores relevantes de cada post.
Agora você pode editar o elemento de postagem recém-criado para vincular partes da marcação aos dados na sua planilha. Para isso, vamos introduzir um atributo post, que vai ler o título, o autor, o conteúdo e outros campos que criamos anteriormente. O atributo selected (que vamos preencher mais tarde) é usado para mostrar uma postagem apenas se um 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 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 de HTML, como gostaríamos que ele aparecesse 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 /elements, desta vez importando post.html e incluindo <post-element> na 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 que o elemento do blog fosse importado usando importações de HTML (uma maneira de incluir e reutilizar documentos HTML em outros documentos HTML) para 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
Em seguida, vamos editar nosso elemento para usar o elemento utilitário Polymer JSONP para ler posts.json. Para acessar o adaptador, clone o repositório com o Git ou instale polymer-elements pelo Bower executando bower install polymer-elements.
Depois de ter o utilitário, você precisará incluí-lo 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 para ele e forneça o url para nossa planilha de postagens do blog, 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, podemos adicionar modelos para iterar na planilha depois que ela for lida. O primeiro gera um sumário, com um título vinculado para 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>
O segundo renderiza uma instância de post-element para cada entrada encontrada, transmitindo o conteúdo da postagem para ela. Observe que estamos transmitindo um atributo post que representa o conteúdo da postagem para 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 [[ vinculações ]] para cada elemento na coleção de matrizes das nossas postagens, quando fornecido.
Agora, para preencher o [[route]] atual, vamos usar uma biblioteca chamada Flatiron Director, que se vincula ao [[route]] sempre que o hash do URL muda.
Felizmente, há um elemento Polymer (parte do pacote more-elements) que podemos usar para isso. Depois de copiado para o diretório /elements, podemos referenciá-lo com <flatiron-director route="[[route]]" autoHash></flatiron-director>, especificando route como a propriedade que queremos vincular e instruindo-o a ler automaticamente o valor de qualquer alteração de hash (autoHash).
Ao juntar 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>
Uhu! Agora temos um blog simples que lê dados de JSON e usa dois elementos do Polymer estruturados com o Yeoman.
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 o customelements.io. Analisando os elementos criados pela comunidade, encontrei um para buscar perfis do Gravatar, que podemos pegar e adicionar ao nosso site de blog também.
Copie as origens do elemento gravatar para o diretório /elements, inclua-o por meio de importações de HTML em post.html e adicione
<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á:
Lindo!
Em um tempo relativamente curto, criamos um aplicativo simples composto por vários componentes da Web sem precisar nos preocupar em escrever código boilerplate, baixar dependências manualmente ou configurar um servidor local ou um 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 Gruntfile) para produzir uma versão otimizada do seu aplicativo. A execução de grunt por si só vai executar uma tarefa default que o gerador configurou para linting, teste e build:
grunt.registerTask('default', [
'jshint',
'test',
'build'
]);
A tarefa jshint acima vai verificar o arquivo .jshintrc para saber suas preferências e, em seguida, vai executá-lo em todos os arquivos JavaScript do projeto. Para conferir todas as opções do JSHint, consulte a documentação.
A tarefa test é mais ou menos assim e pode criar e disponibilizar seu app para o framework de teste que recomendamos, o Mocha. Ele também vai executar os testes para você:
grunt.registerTask('test', [
'clean:server',
'createDefaultTemplate',
'jst',
'compass',
'connect:test',
'mocha'
]);
Como nosso app neste caso é bastante simples, vamos deixar a criação de testes como um exercício separado para você. Há mais algumas coisas que o processo de build precisa processar. 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 para criar uma versão do app pronta para produção e envio. Vamos fazer um teste.
Pronto.
Se você tiver dificuldades, uma versão pré-criada do polymer-blog está disponível para consulta em https://github.com/addyosmani/polymer-blog.
O que mais temos reservado?
Os componentes da Web ainda estão em um estado de evolução, assim como as ferramentas ao redor deles.
No momento, estamos analisando como concatenar as importações de HTML para melhorar o desempenho 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 quando tivermos respostas melhores para essas perguntas, mas há muitos momentos empolgantes pela frente.
Como instalar o Polymer independente com o Bower
Se você preferir um início mais leve para o Polymer, instale-o de forma independente diretamente do Bower executando:
bower install polymer
que vai adicioná-lo ao diretório bower_components. Depois, você pode fazer referência a ele no índice do aplicativo manualmente e arrasar no futuro.
O que você acha?
Agora você sabe como criar um app Polymer usando componentes da Web com o Yeoman. Se você tiver feedback sobre o gerador, deixe um comentário, registre um bug ou poste no Issue Tracker do Yeoman. Queremos saber se há mais alguma coisa que você gostaria que o gerador fizesse melhor, já que só podemos melhorar com seu uso e feedback :)