運用 Yeoman 和 Polymer 建構網頁應用程式

使用新型工具在網頁應用程式僅供內部使用

Addy Osmani
Addy Osmani

簡介

Allo 是 Allo 的好幫手。任何人只要編寫網頁應用程式,都知道維持高效率的工作效率。這可不是您需要擔心這些繁瑣的工作,例如尋找適合的樣板、設定開發與測試工作流程,以及壓縮和壓縮所有來源。

幸運的是,現代的前端工具可自動執行大部分的工作,讓您專心編寫啟動式應用程式。本文將示範如何使用 Yeoman;這是網頁應用程式工具的工作流程,可簡化使用 Polymer 的聚填與糖,利用網頁元件開發應用程式。

Yeoman

認識 Yo、Grunt 和 Bower

Yeoman 是一名帽子,當中包含三種提升效率的工具:

  • yo 是一款鷹架工具,提供由架構專屬的 Scaffold 構成的生態系統 (稱為產生器),可用於執行先前提及的部分繁瑣工作。
  • grunt 用於建構、預覽及測試專案,這都要歸功於 Yeoman 團隊規劃的工作和 grunt-contrib 所規劃的。
  • bower 時使用依附元件管理機制,這樣您就不用再手動下載及管理指令碼。

只要使用一、兩個指令,Yoman 就能編寫應用程式的樣板程式碼 (或 Models 等個別部分)、編譯您的 Sass,盡可能減少及串連 CSS、JS、HTML 和圖片,並啟動目前目錄中的簡易網路伺服器。此外,它也可以執行單元測試等等。

您可以從 Node Packaged Modules (npm) 中安裝產生器,且現在有超過 220 個產生器可用,其中有許多是由開放原始碼社群編寫的產生器。常見的發電機包括 generator-angulargenerator-backbonegenerator-ember

Yeoman 首頁

已安裝較新版本的 Node.js 後,請前往距離最近的終端機並執行:

$ npm install -g yo

大功告成!現在您已經有 Yo、Grunt 和 Bower,可以直接透過指令列執行這些指令。以下是執行 yo 的輸出內容:

葉曼安裝

聚合物產生器

如同我先前所提,Polymer 是聚合物和糖的程式庫,可讓您在新型瀏覽器中使用網頁元件。透過這項專案,開發人員可以使用未來的平台建構應用程式,並向 W3C 告知有哪些地方的飛行規格需要進一步改善。

聚合物產生器異物

generator-polymer 是全新的產生器,能協助你使用 Yeoman 去除 Polymer 應用程式,以便透過指令列輕鬆建立及自訂 Polymer (自訂) 元素,並使用 HTML Imports 匯入。自動代您編寫樣板程式碼,節省寶貴時間。

接下來,請執行下列指令,安裝 Polymer 的產生器:

$ npm install generator-polymer -g

就是這麼簡單!現在,您的應用程式擁有超能力!

您可以使用下列新安裝的發電機存取某些特定功能:

  • polymer:element 可用於隔離新的個別 Polymer 元素。例如:yo polymer:element carousel
  • polymer:app 可用於管理初始 index.html;這是 Gruntfile.js,其中包含專案的建構時間設定、Grunt 工作和建議的專案資料夾結構。您也可以選用 Sass Bootstrap 設定專案樣式。

開始建構 Polymer 應用程式

我們將使用一些自訂 Polymer 元素和新的產生器,建立一個簡單的網誌。

Polymer 應用程式

若要開始,請前往終端機,使用 mkdir my-new-project && cd $_ 建立新目錄並 cd 進入該終端機。您現在可以執行下列指令,啟動 Polymer 應用程式:

$ yo polymer
Polymer 應用程式建構

這會從 Bower 和 Scaffold 取得最新版本的 Polymer,並產生了 index.html、目錄結構和 Grunt 工作。在等待應用程式準備就緒的同時,你不妨喝杯咖啡,

好的,接下來我們可以執行 grunt server 預覽應用程式的外觀:

代管伺服器

伺服器支援 LiveReload,因此您可以啟動文字編輯器、編輯自訂元素,瀏覽器會在儲存後重新載入。方便您即時瞭解應用程式目前的狀態。

接著,我們要建立新的 Polymer 元素來代表網誌文章。

$ yo polymer:element post
建立文章元素

Yeoman 會問幾個問題,例如是否要加入建構函式,或是使用 HTML Import 將文章元素加入 index.html。假設前兩個選項目前都沒有,請將第三個選項留空。

$ 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

這項操作會在 /elements 目錄中建立名為 post.html 的 Polymer 元素:

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

內容如下:

使用實際資料來源

我們的網誌需要一個地方,才能撰寫及閱讀新文章。為了示範如何使用真實的資料服務,我們將使用 Google Apps Sheets API。如此一來,我們就可以輕鬆閱讀任何透過「Google 文件」建立的試算表的內容。

讓我們協助您完成以下設定:

  1. 透過瀏覽器 (建議您執行下列步驟) 開啟這份 Google 文件試算表。它包含下列欄位的貼文資料範例:

    • ID
    • 標題
    • 作者
    • 內容
    • 日期
    • 關鍵字
    • 電子郵件 (作者)
    • 插入 (用於文章的模糊網址)
  2. 前往 [檔案] 選單,然後選取 [建立副本],即可為試算表建立副本。您可以隨意編輯內容、新增或移除貼文。

  3. 再次前往「檔案」選單,然後選取「發布到網路」。

  4. 按一下「開始發布」

  5. 在「取得已發布資料的連結」下方,從最後一個文字方塊複製網址中的部分。格式為:https://docs.google.com/spreadsheet/ccc?key=0AhcraNy3sgspdDhuQ2pvN21JVW9NeVA0M1h4eGo3RGc#gid=0

  6. 貼到下列網址中 your-key-goes-here 的網址:https://spreadsheets.google.com/feeds/list/your-key-goes-here/od6/public/values?alt=json-in-script&callback=。使用上方的鍵可能看起來像 https://spreadsheets.google.com/feeds/list/0AhcraNy3sgspdDhuQ2pvN21JVW9NeVA0M1h4eGo3RGc/od6/public/values?alt=json-in-script

  7. 您可以將該網址貼入瀏覽器,並前往該網址查看網誌內容的 JSON 版本。記下網址,然後花一些時間查看這項資料的格式,因為您必須反覆查看網址,以便稍後在畫面中顯示。

您瀏覽器中的 JSON 輸出可能有點困難,不過請放心!我們只想瞭解你的貼文資料。

Google 試算表 API 會使用特殊前置字元 post.gsx$ 輸出網誌試算表中的每個欄位。例如:post.gsx$title.$tpost.gsx$author.$tpost.gsx$content.$t 等。我們疊代 JSON 輸出內容中的每個「資料列」時,會參照這些欄位,取得每篇貼文的相關值。

您現在可以編輯新增的鷹架文章元素,將標記的部分標記bind至試算表中的資料。為此,我們導入了一個屬性 post,用於讀取文章標題、作者、內容以及先前建立的其他欄位。selected 屬性 (稍後將填入) 只會用於在使用者前往正確的階層顯示貼文。

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

接下來,我們要執行 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

這次我們要使用「HTML 匯入」匯入網誌,將網誌匯入 index.html,這樣就能在網頁中顯示這些內容。針對第三個提示,我們指定 post.html 做為我們要納入的元素。

和先前一樣,系統會建立新的元素檔案 (blog.html) 並新增至 /elements,這次匯入 post.html 並在範本標記內納入 <post-element>

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

如同我們要求使用 HTML 匯入功能匯入網誌元素 (這是加入並重複使用其他 HTML 文件中 HTML 文件的方法) 至索引,我們也可以確認該元素是否已正確新增至文件 <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>

吹得太好了!

使用 Bower 新增依附元件

接下來,讓我們編輯元素,使用 Polymer JSONP 公用程式元素,在 post.json 中讀取。您可以透過 Git 複製存放區,或是執行 bower install polymer-elements 透過 Bower 安裝 polymer-elements,藉此取得轉接程式。

排水器依附元件

取得這個公用程式後,您必須將其新增為 blog.html 元素中的匯入項目,如下所示:

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

接下來,請加入該標記的標記,像之前一樣將 url 提供給我們的網誌文章試算表,並在結尾加上 &callback=

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

設定妥當後,我們現在可以新增範本,在系統讀取試算表內容後反覆調整。第一個輸出會輸出目錄,針對文章標題指向文章的連結。

<!-- 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-element 例項,並據此將貼文內容傳遞至該項目。請注意,我們會透過代表單一試算表列的 post 屬性和 selected 屬性傳遞路徑,該屬性會填入路線。

<!-- Post content -->

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

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

</template>

您在範本中使用的 repeat 屬性如果有提供 (如果有的話),會為貼文陣列集合中的每個元素建立及維護 [[ bindings ]]。

Polymer 應用程式

為方便我們填入目前的 [[route]],我們將使用 Flatiron 目錄,並在網址雜湊變更時綁定 [[route]]。

幸好有 Polymer 元素 (屬於更多元素套件的一部分) 可以拿取。複製到 /elements 目錄後,我們可以使用 <flatiron-director route="[[route]]" autoHash></flatiron-director> 參照該目錄,並將 route 指定為要繫結的屬性,並指示它自動讀取所有雜湊變更的值 (autoHash)。

現在我們只要將所有內容彙整在一起:

    <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>
Polymer 應用程式

太棒了!現在,我們有一個簡單的網誌,負責讀取 JSON 的資料,並且使用兩個與 Yeoman 相制的 Polymer 元素。

使用第三方元素

網頁元件的元素生態系統最近不斷成長,元件庫網站 (例如 customelements.io) 會開始出現。在瀏覽社群建立的元素後,我找到一個可用來擷取顆粒的個人資料,我們可以擷取並新增到我們的網誌網站。

自訂元素首頁

將 gravatar 元素來源複製到您的 /elements 目錄,透過 post.html 中的 HTML 匯入功能將其加入範本,然後將 新增至範本,然後傳入試算表的電子郵件欄位做為使用者名稱來源。好極了!

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

我們來看看這項數據帶來的好處:

含有自訂元素的 Polymer 應用程式

太棒了!

在短時間內,我們建立了由數個網頁元件組成的簡易應用程式,不必擔心編寫樣板程式碼、手動下載依附元件,或是設定本機伺服器或建構工作流程。

最佳化應用程式

Yeoman 工作流程包含另一項名為 Grunt 的開放原始碼專案,這個工作執行器可執行各種建構特定工作 (在 Gruntfile 中定義),以產生最佳化的應用程式版本。自行執行 grunt 將會執行產生器為程式碼檢查、測試和建構設定的 default 工作:

grunt.registerTask('default', [

    'jshint',

    'test',

    'build'

]);

上述 jshint 工作會向 .jshintrc 檔案檢查,判斷你的偏好設定,然後針對專案中的所有 JavaScript 檔案執行該工作。如要全面瞭解 JSHint 提供的選項,請參閱說明文件

test 工作看起來大致如下,可針對測試架構,建立並提供應用程式,做為測試架構中建議立即使用的測試架構 Mocha。這項操作也會為您執行測試:

grunt.registerTask('test', [

    'clean:server',

    'createDefaultTemplate',

    'jst',

    'compass',

    'connect:test',

    'mocha'

]);

由於這個示例中的應用程式相當簡化,因此我們會代替您編寫測試。還需要建構建構程序處理常式,因此請來看看 Gruntfile.js 中定義的 grunt build 工作會執行哪些操作:

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

]);

執行 grunt build,且應建構可用於正式版的應用程式。我們來試試看。

搜索版本

成功!

如果遇到問題,可以使用預先建構的 polymer-blog 版本,前往 https://github.com/addyosmani/polymer-blog

店內還有哪些地方要推出?

網頁元件仍處於演進階段,因此周遭元件仍是相關工具。

我們目前正在觀察如何將自己的 HTML 匯入內容串連起來,透過 Vulcanize (Polymer 專案的工具) (Polymer 專案提供的工具) 來改善載入 HTML 的運作效能,以及元件生態系統可能如何與 Bower 等套件管理員合作。

針對這些問題有更好的解答,我們也會通知您,但各位日後還有許多令人興奮的時刻。

使用 Bower 單獨安裝 Polymer

如果你想從較輕量的啟動方式開始至 Polymer,可以執行下列指令,直接從 Bower 安裝:

bower install polymer

將其新增至 bower_components 目錄如此一來,您就可以手動在應用程式索引中參照它,並推動未來。

您覺得如何?

您已瞭解如何搭配 Yeoman 使用網頁元件,將 Polymer 應用程式合成。如果您對產生器提供意見,請在留言中告訴我們;或者回報錯誤或張貼至 Yeoman Issue Tracker。歡迎您提供寶貴意見,協助我們提升產生器的效能,因為這項工具只限於使用使用情形,而我們覺得我們可以予以改進 :)