Crea lo scaffolding delle tue app web con strumenti moderni
Introduzione
Ciao. Chiunque scriva un'app web sa quanto sia importante mantenere la produttività. È una sfida quando devi preoccuparti di attività noiose come trovare il boilerplate giusto, configurare un flusso di lavoro di sviluppo e test e ridurre al minimo e comprimere tutte le sorgenti.
Fortunatamente, gli strumenti di frontend moderni possono aiutarti ad automatizzare gran parte di queste operazioni, lasciandoti concentrare sulla scrittura di un'app straordinaria. Questo articolo ti mostrerà come utilizzare Yeoman, un flusso di lavoro di strumenti per app web per semplificare la creazione di app utilizzando Polymer, una libreria di polyfill e sugar per lo sviluppo di app utilizzando componenti web.
Ti presentiamo Yo, Grunt e Bower
Yeoman è un uomo con un cappello e tre strumenti per migliorare la produttività:
- yo è uno strumento di scaffolding che offre un ecosistema di strutture specifiche per il framework, chiamate generatori, che possono essere utilizzate per eseguire alcune delle noiose attività che ho menzionato in precedenza.
- grunt viene utilizzato per compilare, visualizzare l'anteprima e testare il progetto, grazie all'aiuto delle attività selezionate dal team di Yeoman e grunt-contrib.
- bower viene utilizzato per la gestione delle dipendenze, in modo da non dover più scaricare e gestire manualmente gli script.
Con un paio di comandi, Yeoman può scrivere codice boilerplate per la tua app (o singoli componenti come i modelli), compilare il codice Sass, ridurre al minimo e concatenare CSS, JS, HTML e immagini e avviare un semplice server web nella tua directory corrente. Può anche eseguire i test delle unità e altro ancora.
Puoi installare i generatori da Node Packaged Modules (npm) e sono ora disponibili oltre 220 generatori, molti dei quali sono stati scritti dalla community open source. I generatori più utilizzati sono generator-angular, generator-backbone e generator-ember.
Con una versione recente di Node.js installata, vai al terminale più vicino ed esegui:
$ npm install -g yo
È tutto. Ora hai Yo, Grunt e Bower e puoi eseguirli direttamente dalla riga di comando. Ecco l'output dell'esecuzione di yo
:
Generatore di polimeri
Come accennato in precedenza, Polymer è una libreria di polyfill e sugar che consente l'utilizzo dei componenti web nei browser moderni. Il progetto consente agli sviluppatori di creare app utilizzando la piattaforma del futuro e di informare il W3C dei punti in cui le specifiche in-flight possono essere ulteriormente migliorate.
generator-polymer è un nuovo generatore che ti aiuta a creare lo schema delle app Polymer utilizzando Yeoman, consentendoti di creare e personalizzare facilmente gli elementi Polymer (personalizzati) tramite la riga di comando e di importarli utilizzando le importazioni HTML. In questo modo risparmi tempo perché il codice boilerplate viene scritto per te.
Poi, installa il generatore di Polymer eseguendo:
$ npm install generator-polymer -g
È tutto. Ora la tua app ha i superpoteri dei componenti web.
Il nostro generatore appena installato ha alcuni elementi specifici a cui avrai accesso:
polymer:element
viene utilizzato per creare la struttura di nuovi singoli elementi Polymer. Ad esempio:yo polymer:element carousel
polymer:app
viene utilizzato per creare lo schema dell'index.html iniziale, un Gruntfile.js contenente la configurazione in fase di compilazione per il progetto, nonché le attività Grunt e una struttura di cartelle consigliata per il progetto. Ti darà anche la possibilità di utilizzare Sass Bootstrap per gli stili del tuo progetto.
Creiamo un'app Polymer
Creeremo un semplice blog utilizzando alcuni elementi Polymer personalizzati e il nostro nuovo generatore.
Per iniziare, vai al terminale, crea una nuova directory e accedi utilizzando mkdir my-new-project && cd $_
. Ora puoi avviare la tua app Polymer eseguendo:
$ yo polymer
In questo modo viene recuperata la versione più recente di Polymer da Bower e viene creata la struttura di directory e le attività Grunt per il tuo flusso di lavoro. Perché non prendi un caffè mentre aspettiamo che l'app sia pronta?
Bene, ora possiamo eseguire grunt server
per visualizzare l'anteprima dell'app:
Il server supporta LiveReload, il che significa che puoi avviare un editor di testo, modificare un elemento personalizzato e il browser verrà ricaricato al salvataggio. In questo modo puoi avere una bella visualizzazione in tempo reale dello stato attuale della tua app.
A questo punto, creiamo un nuovo elemento Polymer per rappresentare un post del blog.
$ yo polymer:element post
Yeoman ci pone alcune domande, ad esempio se vogliamo includere un costruttore o utilizzare un'importazione HTML per includere l'elemento post in index.html
. Per il momento diciamo No alle prime due opzioni e lasciamo vuota la terza.
$ 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
Verrà creato un nuovo elemento Polymer nella directory /elements
denominato 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>
Contiene:
- Codice boilerplate per l'elemento personalizzato, che ti consente di utilizzare un tipo di elemento DOM personalizzato nella pagina (ad es.
<post-element>
) - Un tag modello per la creazione di modelli lato client "nativo" e stili basati su ambito di esempio per incapsulare gli stili dell'elemento
- Boilerplate per la registrazione degli elementi ed eventi del ciclo di vita.
Utilizzo di una fonte di dati reale
Il nostro blog avrà bisogno di un luogo in cui scrivere e leggere nuovi post. Per dimostrare come utilizzare un servizio di dati reale, utilizzeremo l'API Fogli Google Apps. In questo modo possiamo leggere facilmente i contenuti di qualsiasi foglio di lavoro creato utilizzando Documenti Google.
Ecco come procedere:
Nel browser (per questi passaggi è consigliato Chrome), apri questo foglio di lavoro di Documenti Google. Contiene dati di post di esempio nei seguenti campi:
- ID
- Titolo
- Autore
- Contenuti
- Data
- Parole chiave
- Indirizzo email (dell'autore)
- Slug (per l'URL dello slug del post)
Vai al menu File e seleziona Crea una copia per creare la tua copia del foglio di lavoro. Puoi modificare i contenuti a tuo piacimento, aggiungendo o rimuovendo post.
Vai di nuovo al menu File e seleziona Pubblica sul web.
Fai clic su Inizia pubblicazione.
In Ottieni un link ai dati pubblicati, copia la parte chiave dell'URL fornito dall'ultima casella di testo. Ecco un esempio: https://docs.google.com/spreadsheet/ccc?key=0AhcraNy3sgspdDhuQ2pvN21JVW9NeVA0M1h4eGo3RGc#gid=0
Incolla la chiave nel seguente URL dove è presente qui-va-la-chiave: https://spreadsheets.google.com/feeds/list/your-key-goes-here/od6/public/values?alt=json-in-script&callback=. Un esempio che utilizza la chiave riportata sopra potrebbe essere https://spreadsheets.google.com/feeds/list/0AhcraNy3sgspdDhuQ2pvN21JVW9NeVA0M1h4eGo3RGc/od6/public/values?alt=json-in-script.
Puoi incollare l'URL nel browser e aprirlo per visualizzare la versione JSON dei contenuti del tuo blog. Prendi nota dell'URL, poi dedica un po' di tempo a esaminare il formato di questi dati, poiché dovrai eseguirne l'iterazione per visualizzarli in un secondo momento sullo schermo.
L'output JSON nel browser potrebbe sembrare un po' complicato, ma non preoccuparti. Ci interessano solo i dati relativi ai tuoi post.
L'API Fogli Google genera un output per ogni campo del foglio di lavoro del blog con un prefisso speciale post.gsx$
. Ad esempio: post.gsx$title.$t
, post.gsx$author.$t
, post.gsx$content.$t
e così via. Quando eseguiamo l'iterazione su ogni "riga" nell'output JSON, faremo riferimento a questi campi per recuperare i valori pertinenti per ogni post.
Ora puoi modificare l'elemento post appena creato per legare parti di markup ai dati nel foglio di lavoro. A tale scopo, introduciamo un attributo post
, che verrà letto per il titolo, l'autore, i contenuti e altri campi del post che abbiamo creato in precedenza. L'attributo selected
(che verrà compilato in un secondo momento) viene utilizzato per mostrare un post solo se un utente accede allo slug corretto.
<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>
Ora, esegui yo polymer:element blog
per creare un elemento del blog che contenga sia una raccolta di post sia il layout del 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
Questa volta importiamo il blog in index.html utilizzando le importazioni HTML come vogliamo che venga visualizzato nella pagina. Nello specifico, per il terzo prompt specifichiamo post.html
come elemento da includere.
Come prima, viene creato un nuovo file elemento (blog.html) e aggiunto a /elements, questa volta importando post.html e includendo <post-element>
all'interno del tag del modello:
<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>
Poiché abbiamo chiesto di importare l'elemento del blog nel nostro indice utilizzando le importazioni HTML (un modo per includere e riutilizzare i documenti HTML in altri documenti HTML), possiamo anche verificare che sia stato aggiunto correttamente al 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>
Fantastico.
Aggiunta di dipendenze utilizzando Bower
A questo punto, modifichiamo l'elemento per utilizzare l'elemento di utilità Polymer JSONP per leggere il file posts.json. Puoi ottenere l'adattatore clonando il repository con Git o installando polymer-elements
tramite Bower eseguendo bower install polymer-elements
.
Una volta ottenuta l'utilità, dovrai includerla come importazione nell'elemento blog.html con:
<link rel="import" href="../bower_components/polymer-jsonp/polymer-jsonp.html">
Poi, includi il tag e fornisci il url
al foglio di lavoro dei post del blog di cui sopra, aggiungendo &callback=
alla fine:
<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>
A questo punto, possiamo aggiungere modelli per eseguire l'iterazione sul foglio di lavoro dopo averlo letto. Il primo genera un sommario, con un titolo collegato a un post che rimanda al relativo slug.
<!-- 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>
Il secondo restituisce un'istanza di post-element
per ogni voce trovata, passando i contenuti del post di conseguenza. Tieni presente che stiamo passando un attributo post
che rappresenta i contenuti del post per una singola riga del foglio di lavoro e un attributo selected
che verrà compilato con un percorso.
<!-- Post content -->
<template repeat="[[post in posts.feed.entry]]">
<post-element post="[[post]]" selected="[[route]]"></post-element>
</template>
L'attributo repeat
che vedi utilizzato nel nostro modello crea e gestisce un'istanza con [[ bindings ]] per ogni elemento della raccolta di array dei nostri post, se viene fornito.
Ora, per completare l'attuale [[route]], faremo un po' di furbizia e utilizzeremo una libreria chiamata Flatiron Director che si lega a [[route]] ogni volta che l'hash dell'URL cambia.
Fortunatamente, esiste un elemento Polymer (parte del pacchetto more-elements) che possiamo utilizzare. Una volta copiato nella directory /elements, possiamo fare riferimento a <flatiron-director route="[[route]]" autoHash></flatiron-director>
, specificando route
come proprietà a cui vogliamo eseguire il binding e indicando di leggere automaticamente il valore di eventuali modifiche dell'hash (autoHash).
Mettendo tutto insieme, otteniamo:
<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>
Evviva! Ora abbiamo un semplice blog che legge i dati da JSON e utilizza due elementi Polymer con struttura di base creata con Yeoman.
Lavorare con elementi di terze parti
L'ecosistema di elementi intorno a Web Components è cresciuto di recente con l'inizio della comparsa di siti di gallerie di componenti come customelements.io. Tra gli elementi creati dalla community, ne ho trovato uno per recuperare i profili Gravatar e possiamo anche recuperarlo e aggiungerlo al nostro sito di blog.
Copia le origini degli elementi Gravatar nella directory /elements
, includile tramite le importazioni HTML in post.html e poi aggiungi
<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>
Vediamo cosa ci offre:
Bella!
In un tempo relativamente breve, abbiamo creato una semplice applicazione composta da diversi componenti web senza doverci preoccupare di scrivere codice boilerplate, scaricare manualmente le dipendenze o configurare un server locale o il flusso di lavoro di compilazione.
Ottimizzazione dell'applicazione
Il flusso di lavoro di Yeoman include un altro progetto open source chiamato Grunt, un task runner che può eseguire una serie di attività specifiche per la compilazione (definite in un file Gruntfile) per produrre una versione ottimizzata della tua applicazione. L'esecuzione di grunt
da solo eseguirà un'attività default
configurata dal generatore per il linting, i test e la compilazione:
grunt.registerTask('default', [
'jshint',
'test',
'build'
]);
L'attività jshint
sopra indicata controllerà il file .jshintrc
per conoscere le tue preferenze, quindi lo eseguirà su tutti i file JavaScript del progetto. Per una panoramica completa delle opzioni disponibili con JSHint, consulta la documentazione.
L'attività test
è simile a questa e può creare e pubblicare la tua app per il framework di test consigliato, Mocha. Inoltre, eseguirà i test per te:
grunt.registerTask('test', [
'clean:server',
'createDefaultTemplate',
'jst',
'compass',
'connect:test',
'mocha'
]);
Poiché in questo caso la nostra app è piuttosto semplice, lasceremo a te la scrittura dei test come esercizio a parte. Ci sono altre cose che dovremo gestire con il nostro processo di compilazione, quindi diamo un'occhiata a cosa farà l'attività grunt build
definita nel nostro Gruntfile.js
:
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
]);
Esegui grunt build
e verrà creata una versione pronta per la produzione della tua app, che potrai quindi rilasciare. Proviamo.
Operazione riuscita.
Se non riesci a procedere, puoi consultare una versione precompilata di polymer-blog all'indirizzo https://github.com/addyosmani/polymer-blog.
Cosa c'è di più?
I componenti web sono ancora in fase di evoluzione e, di conseguenza, lo sono anche gli strumenti a loro dedicati.
Al momento stiamo valutando come concatenare le importazioni HTML per migliorare le prestazioni di caricamento tramite progetti come Vulcanize (uno strumento del progetto Polymer) e come l'ecosistema dei componenti potrebbe funzionare con un gestore dei pacchetti come Bower.
Ti aggiorneremo non appena avremo risposte migliori a queste domande, ma ci aspettano molti momenti emozionanti.
Installazione di Polymer autonoma con Bower
Se preferisci iniziare con una versione più leggera di Polymer, puoi installarlo come singolo componente direttamente da Bower eseguendo:
bower install polymer
che lo aggiungerà alla directory bower_components. Potrai quindi farvi riferimento manualmente nell'indice delle applicazioni e farti strada nel futuro.
Cosa ne pensi?
Ora sai come creare lo schema di un'app Polymer utilizzando i componenti web con Yeoman. Se hai feedback sul generatore, non esitare a comunicarcelo nei commenti o a segnalare un bug o un post nel tracker dei problemi di Yeoman. Ci piacerebbe sapere se c'è qualcos'altro che vorresti che il generatore facesse meglio, perché possiamo migliorare solo grazie al tuo utilizzo e al tuo feedback.