Creazione di app web con Yeoman e Polymer

Affronta le tue app web con strumenti moderni

Addy Osmani
Addy Osmani

Introduzione

Allo". Chiunque scriva un'app web sa quanto sia importante mantenere la propria produttività. È molto difficile quando devi preoccuparti di attività noiose come trovare il boilerplate giusto, impostare un flusso di lavoro di sviluppo e test e minimizzare e comprimere tutte le tue origini.

Fortunatamente, gli strumenti front-end moderni sono utili per automatizzare gran parte di questo processo, permettendoti di concentrarti sulla scrittura di un'app straordinaria. Questo articolo ti mostrerà come utilizzare Yeoman, un flusso di lavoro di strumenti per le app web che consentono di semplificare la creazione di app mediante Polymer, una libreria di polyfill e zucchero per lo sviluppo di app mediante componenti web.

Yeoman

Ti presentiamo Yo, Grunt e Bower

Yeoman ha tre strumenti per migliorare la tua produttività:

  • yo è uno strumento di "scaffolding" che offre un ecosistema di scaffold specifici per framework, chiamati generatori, che possono essere utilizzati per eseguire alcune delle noiose attività che ho menzionato in precedenza.
  • grunt viene utilizzato per creare, visualizzare in anteprima e testare il tuo progetto grazie all'aiuto delle attività selezionate dal team Yeoman e del grunt-contrib.
  • bower viene utilizzato per la gestione delle dipendenze, in modo da non dover più scaricare e gestire manualmente gli script.

Con uno o due comandi, Yeoman può scrivere codice boilerplate per la tua app (o singole parti come Modelli), compilare il tuo Sass, ridurre al minimo e concatenare CSS, JS, HTML e immagini e avviare un semplice server web nella tua directory corrente. Può anche eseguire test delle unità e altro ancora.

Puoi installare generatori da Node Packaged Modules (npm) e ora ce ne sono più di 220 generatori, molti dei quali sono stati scritti dalla community open source. I generatori più diffusi includono generator-angular, generator-backbone e generator-ember.

Home page di Yeoman

Dopo avere installato una versione recente di Node.js, 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:

Installazione Yeoman

Generatore di polimeri

Come ho già detto, Polymer è una libreria di polyfill e zucchero che consente l'utilizzo dei componenti web nei browser moderni. Il progetto consente agli sviluppatori di creare app utilizzando la piattaforma di domani e di comunicare al W3C i luoghi in cui le specifiche dei voli possono essere ulteriormente migliorate.

Home page del generatore di polimeri

generator-polymer è un nuovo generatore che ti aiuta a strutturare app Polymer utilizzando Yeoman, permettendoti di creare e personalizzare facilmente elementi Polymer (personalizzati) tramite la riga di comando e importarli usando le importazioni HTML. In questo modo puoi risparmiare tempo scrivendo il codice boilerplate per te.

Quindi, installa il generatore Polymer eseguendo:

$ npm install generator-polymer -g

È tutto. Ora la tua app ha i superpoteri dei componenti web.

Il generatore appena installato ha alcuni bit specifici a cui avrai accesso:

  • polymer:element viene utilizzato per strutturare nuovi elementi singoli Polymer. Ad esempio: yo polymer:element carousel
  • polymer:app viene usato per strutturare il tuo index.html iniziale, un file Gruntfile.js contenente la configurazione in fase di compilazione per il tuo progetto, nonché attività Grunt e una struttura di cartelle consigliata per il progetto. Ti darà anche la possibilità di usare il bootstrap di Sass per gli stili del tuo progetto.

Creiamo un'app Polymer

Creeremo un semplice blog utilizzando alcuni elementi Polymer personalizzati e il nostro nuovo generatore.

App Polymer

Per iniziare, vai al terminale, crea una nuova directory e inserisci CD utilizzando mkdir my-new-project && cd $_. Ora puoi avviare la tua app Polymer eseguendo:

$ yo polymer
Creazione di app Polymer

Questo ottiene l'ultima versione di Polymer da Bower e organizza un index.html, una struttura di directory e attività Grunt per il tuo flusso di lavoro. Perché non prendi un caffè mentre attendiamo che l'app finisca di prepararsi?

Bene, adesso possiamo eseguire grunt server per visualizzare l'anteprima dell'aspetto dell'app:

Server Grunt

Il server supporta LiveReload, il che significa che puoi attivare un editor di testo, modificare un elemento personalizzato e, al momento del salvataggio, il browser verrà ricaricato. In questo modo avrai una bella visione in tempo reale dello stato attuale della tua app.

Ora creiamo un nuovo elemento Polymer per rappresentare un post del blog.

$ yo polymer:element post
Crea elemento del 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. Diciamo No alle prime due opzioni per il momento e lascia 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

Viene creato un nuovo elemento Polymer nella directory /elements denominata 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:

Lavorare con un'origine di dati reale

Il nostro blog avrà bisogno di un posto dove scrivere e leggere nuovi post. Per dimostrare come si lavora con un servizio dati reale, utilizzeremo l'API Fogli di lavoro di Google Apps. Questo ci consente di leggere facilmente il contenuto di qualsiasi foglio di lavoro creato con Documenti Google.

Iniziamo la configurazione:

  1. Nel browser (per questa procedura è consigliabile utilizzare Chrome) apri questo foglio di lavoro di Documenti Google. Contiene dati di esempio relativi ai post nei seguenti campi:

    • ID
    • Titolo
    • Autore
    • Contenuti
    • Data
    • Parole chiave
    • Email (dell'autore)
    • Slug (per l'URL slug del post)
  2. Vai al menu File e seleziona Crea una copia per creare la tua copia del foglio di lavoro. Puoi modificare i contenuti quando preferisci, aggiungere o rimuovere post.

  3. Vai di nuovo al menu File e seleziona Pubblica sul web.

  4. Fai clic su Inizia a pubblicare

  5. In Ottieni un link ai dati pubblicati, copia la parte relativa alla chiave dall'ultima casella di testo dell'URL fornito. Ha un aspetto simile a questo: https://docs.google.com/spreadsheet/ccc?key=0AhcraNy3sgspdDhuQ2pvN21JVW9NeVA0M1h4eGo3RGc#gid=0

  6. Incolla la chiave nel seguente URL, in corrispondenza di la-tua-chiave-goes-qui: https://spreadsheets.google.com/feeds/list/your-key-goes-here/od6/public/values?alt=json-in-script&callback=. Un esempio che utilizza la chiave sopra riportata potrebbe avere il seguente aspetto: https://spreadsheets.google.com/feeds/list/0AhcraNy3sgspdDhuQ2pvN21JVW9NeVA0M1h4eGo3RGc/od6/public/values?alt=json-in-script

  7. 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 rivedere il formato di questi dati poiché dovrai ripeterlo per visualizzarli sullo schermo in un secondo momento.

L'output JSON nel browser può sembrare un po' scoraggiante, ma non preoccuparti. Ci interessano solo i dati relativi ai tuoi post.

L'API Google Fogli di lavoro restituisce 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" dell'output JSON, faremo riferimento a questi campi per recuperare i valori pertinenti per ogni post.

Ora puoi modificare l'elemento post strutturato appena aggiunto per bind parti di markup ai dati nel foglio di lavoro. A tale scopo, introduciamo un attributo post, che leggerà il titolo del post, l'autore, i contenuti e altri campi creati in precedenza. L'attributo selected (che completeremo in seguito) 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>

Successivamente, creiamo un elemento blog che contenga sia una raccolta di post sia il layout del tuo blog eseguendo 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

Questa volta il blog viene importato in index.html utilizzando le importazioni HTML, poiché vogliamo che venga visualizzato nella pagina. Per il terzo prompt specifico, specifichiamo post.html come elemento da includere.

Come in precedenza, viene creato un nuovo file dell'elemento (blog.html) e aggiunto a /elements, questa volta importando post.html e includendo <post-element> nel 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 che l'elemento del blog venga importato nel nostro indice utilizzando le importazioni HTML (un modo per includere e riutilizzare 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 mediante Bower

Successivamente, modifichiamo l'elemento in modo da utilizzare l'elemento di utilità Polymer JSONP da leggere in posts.json. Puoi ottenere l'adattatore clonando il repository git o installando polymer-elements tramite Bower eseguendo bower install polymer-elements.

Dipendenze boower

Una volta che hai l'utilità, dovrai includerla come importazione nel tuo elemento blog.html con:

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

Quindi includi il tag corrispondente e fornisci url al foglio di lavoro dei post del blog di prima, 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>

Con questa impostazione, possiamo aggiungere modelli da ripetere nel nostro foglio di lavoro una volta che è stato letto. Il primo genera un sommario, con un titolo collegato per un post che punta allo 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 esegue il rendering di un'istanza di post-element per ogni voce trovata, trasmettendo i contenuti del post di conseguenza. Nota che stiamo passando attraverso un attributo post che rappresenta i contenuti del post per una singola riga del foglio di lavoro e un attributo selected che completeremo 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 essere utilizzato nel nostro modello crea e gestisce un'istanza con [[ associazioni ]] per ogni elemento della raccolta array dei nostri post, quando viene fornito.

App Polymer

Ora, per completare il [[route]] corrente, useremo una libreria chiamata Director Flatiron che si associa al [[route]] ogni volta che cambia l'hash dell'URL.

Per fortuna, possiamo afferrare un elemento Polymer (parte del pacchetto more-elements). Una volta copiata nella directory /elements, possiamo farvi riferimento con <flatiron-director route="[[route]]" autoHash></flatiron-director>, specificando route come proprietà a cui vogliamo eseguire l'associazione e comunicandogli di leggere automaticamente il valore di qualsiasi modifica hash (autoHash).

Riassumendo, ora si ottiene:

    <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

Evviva! Ora abbiamo un semplice blog che legge i dati da JSON e utilizza due elementi Polymer impaldati con Yeoman.

Utilizzare elementi di terze parti

L'ecosistema di elementi relativo ai componenti web è cresciuta di recente con la visualizzazione di siti di gallerie di componenti come customelements.io. Esaminando gli elementi creati dalla community, ne ho trovato uno per recuperare i profili gravatar e possiamo anche trovarlo e aggiungerlo al sito del nostro blog.

Home page degli elementi personalizzati

Copia le origini dell'elemento gravatar nella tua directory /elements, includilo tramite le importazioni HTML in post.html e aggiungi al tuo modello, passando l'email dal nostro foglio di lavoro come origine del nome utente. Evviva!

<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 offre questo:

App Polymer con elementi personalizzati

Bella!

In un tempo relativamente breve, abbiamo creato una semplice applicazione composta da diversi componenti web senza doverti preoccupare di scrivere codice boilerplate, scaricare manualmente le dipendenze o impostare un server locale o creare un flusso di lavoro.

Ottimizzazione dell'applicazione

Il flusso di lavoro di Yeoman include un altro progetto open source chiamato Grunt, ovvero un'esecuzione delle attività in grado di eseguire una serie di attività specifiche della build (definite in un Gruntfile) per produrre una versione ottimizzata della tua applicazione. L'esecuzione di grunt in autonomia eseguirà un'attività di default configurata dal generatore per l'analisi tramite lint, il test e la creazione:

grunt.registerTask('default', [

    'jshint',

    'test',

    'build'

]);

L'attività jshint qui sopra verificherà con il file .jshintrc l'apprendimento delle tue preferenze, quindi la eseguirà su tutti i file JavaScript del tuo progetto. Per visualizzare l'elenco completo delle opzioni con JSHint, consulta la documentazione.

L'attività test ha un aspetto simile a questo e può creare e pubblicare la tua app per il framework di test che consigliamo immediatamente, Mocha. Inoltre, eseguirà automaticamente i test:

grunt.registerTask('test', [

    'clean:server',

    'createDefaultTemplate',

    'jst',

    'compass',

    'connect:test',

    'mocha'

]);

Poiché la nostra app in questo caso è piuttosto semplicissima, ti lasciamo scrivere i test come esercizio separato. Ci sono altre cose di cui avremo bisogno per gestire il processo di compilazione, quindi diamo un'occhiata a cosa farà l'attività grunt build definita in 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 dovresti creare una versione della tua app pronta per la produzione, pronta per la spedizione. Proviamo.

Build Grunt

Operazione riuscita.

Se non riesci a procedere, è disponibile una versione predefinita di polymer-blog all'indirizzo https://github.com/addyosmani/polymer-blog.

Cos'altro abbiamo in serbo per te?

I componenti web sono ancora in uno stato di evoluzione e per questo lo è anche gli strumenti che li circondano.

Attualmente stiamo valutando come si potrebbe procedere con la concatenazione delle importazioni HTML per migliorare le prestazioni di caricamento tramite progetti come Vulcanize (uno strumento del progetto Polymer) e come l'ecosistema per i componenti potrebbe funzionare con un gestore di pacchetti come Bower.

Ti avviseremo man mano che avremo le risposte migliori a queste domande, ma ci saranno molte altre prove entusiasmanti in futuro.

Installazione autonoma di Polymer con Bower

Se preferisci usare Polymer più leggero, puoi installarlo autonomamente direttamente da Bower eseguendo:

bower install polymer

che lo aggiungerà alla directory bower_components. Potrai quindi farvi riferimento manualmente nel tuo indice dell'applicazione e sconvolgere il futuro.

Cosa ne pensi?

Ora sai come strutturare un'app Polymer utilizzando Web Componenti con Yeoman. Se hai feedback sul generatore, faccelo sapere nei commenti, segnala un bug o pubblica un post sul sistema di monitoraggio dei problemi Yeoman. Ci piacerebbe sapere se c'è qualcos'altro che vorresti che il generatore migliorasse, perché possiamo migliorare solo grazie al tuo utilizzo e al tuo feedback :)