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

使用新型工具建構網頁應用程式

Addy Osmani
Addy Osmani

簡介

Allo’ Allo’。任何撰寫網頁應用程式的人,都知道保持工作效率的重要性。您必須擔心繁瑣的任務,例如尋找適當的樣板、設定開發和測試工作流程,以及縮減和壓縮所有來源,這項挑戰相當艱鉅。

幸好,現代化的前端工具可協助自動化大部分的這類工作,讓您能專注於編寫出色的應用程式。本文將說明如何使用 Yeoman,這是一套網頁應用程式工具工作流程,可透過 Polymer 簡化應用程式建立作業。PolymerWeb 元件的 polyfill 和 sugar 程式庫,可用於開發應用程式。

Yeoman

認識 Yo、Grunt 和 Bower

Yeoman 是一位戴著帽子的男性,他提供三項工具協助您提升工作效率:

  • yo 是一種結構架構工具,可提供架構專屬結構架構的生態系統,稱為產生器,可用於執行先前提到的部分繁瑣工作。
  • grunt 可用於建構、預覽及測試專案,這要歸功於 Yeoman 團隊和 grunt-contrib 所收集的任務。
  • bower 可用於依附元件管理,讓您不必再手動下載及管理指令碼。

只要下達一兩個指令,Yeoman 就能為您的應用程式 (或模型等個別部分) 編寫程式碼模板、編譯 Sass、縮減及連結 CSS、JS、HTML 和圖片,並在目前目錄中啟動簡易的網路伺服器。也可以執行單元測試等作業。

您可以從 Node 封裝模組 (npm) 安裝產生器,目前有超過 220 個產生器可供使用,其中許多都是由開放原始碼社群編寫。熱門的產生器包括 generator-angulargenerator-backbonegenerator-ember

Yeoman 首頁

安裝最新版的 Node.js 後,請前往最近的終端機並執行以下指令:

$ npm install -g yo

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

安裝 Yeoman

Polymer Generator

如前所述,Polymer 是 polyfill 和 sugar 的程式庫,可在現代瀏覽器中使用 Web 元件。這個專案可讓開發人員使用未來的平台建構應用程式,並向 W3C 說明在哪些地方可以進一步改善飛行中規格。

Polymer 產生器首頁

generator-polymer 是全新的產生器,可協助您使用 Yeoman 架構出 Polymer 應用程式,讓您輕鬆透過指令列建立及自訂 Polymer (自訂) 元素,並使用 HTML 匯入功能匯入這些元素。這可為您編寫樣板程式碼,節省您的寶貴時間。

接著,執行下列指令來安裝 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 $_ 切換至該目錄。您現在可以執行以下命令,啟動 Polymer 應用程式:

$ yo polymer
Polymer 應用程式建構

這會從 Bower 取得最新版的 Polymer,並為您的工作流程建立 index.html、目錄結構和 Grunt 工作。在等待應用程式準備就緒的同時,不如來杯咖啡吧!

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

Grunt 伺服器

伺服器支援 LiveReload,也就是說您可以啟動文字編輯器、編輯自訂元素,瀏覽器會在儲存時重新載入。這樣一來,您就能即時查看應用程式的目前狀態。

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

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

Yeoman 會詢問我們幾個問題,例如是否要加入建構函式,或是使用 HTML 匯入功能,在 index.html 中加入 Post 元素。我們先對前兩個選項說「否」,第三個選項則留空白。

$ 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 Spreadsheets API。這樣一來,我們就能輕鬆讀取任何使用 Google 文件建立的試算表內容。

讓我們開始設定:

  1. 在瀏覽器 (建議使用 Chrome) 中開啟這個 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 輸出內容中逐一檢視每個「資料列」時,我們會參照這些欄位,找出每個貼文的相關值。

您現在可以編輯新建的結構模板文章元素,將部分標記繫結至試算表中的資料。為此,我們引入了屬性 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 公用元素讀取 posts.json。您可以透過 Git 複製存放區來取得轉接器,也可以執行 bower install polymer-elements 來透過 Bower 安裝 polymer-elements

Bower 依附元件

取得公用程式後,您必須在 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 屬性會在提供時,為文章陣列集合中的每個元素建立並維護具有 [[ 繫結 ]] 的例項。

Polymer 應用程式

為了讓我們填入目前的 [[route]],我們將使用 Flatiron 導覽器程式庫,這個程式庫會在網址雜湊變更時繫結至 [[route]]。

幸好,我們可以使用 Polymer 元素 (more-elements 套件的一部分) 來擷取該元素。複製到 /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 個人資料,我們也可以擷取並將其加入網誌網站。

自訂元素首頁

將 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,並建構可供正式發布的應用程式版本,以便發布。我們來試試看。

Grunt 建構

成功!

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

還有什麼其他產品嗎?

網路元件仍處於發展階段,因此相關工具也是如此。

我們目前正在研究如何透過 Vulcanize (Polymer 專案的工具) 等專案,將 HTML 匯入內容串連起來,進而改善載入效能,以及元件生態系統如何與 Bower 等套件管理工具搭配運作。

我們會在有更明確的答案時通知你,但未來還會有許多令人期待的內容。

使用 Bower 安裝 Polymer 獨立版本

如果您想以較輕量的方式開始使用 Polymer,可以直接從 Bower 安裝獨立版本,方法如下:

bower install polymer

這會將該檔案新增至 bower_components 目錄。接著,您就可以在應用程式索引中手動參照這項資訊,並打造出令人驚豔的未來。

結果如何?

您現在已瞭解如何使用 Yeoman 搭配 Web 元件架構出 Polymer 應用程式。如果您對產生器有任何意見,歡迎在留言中告訴我們,或向 Yeoman 問題追蹤工具回報錯誤或發布貼文。我們很樂意瞭解您希望產生器改善的其他項目,因為只有透過您的使用和意見回饋,我們才能改善產生器 :)