使用現代化工具建構網頁應用程式
簡介
Allo’ Allo’。任何編寫網頁應用程式的人都知道,保持高效率有多重要。如果還得擔心繁瑣的工作,例如尋找合適的樣板、設定開發和測試工作流程,以及縮減和壓縮所有來源,就會是一大挑戰。
幸好,現代前端工具可以協助自動化大部分的工作,讓您專注於編寫出色的應用程式。本文將說明如何使用 Yeoman,這是一套網頁應用程式工具工作流程,可簡化使用 Polymer 建立應用程式的流程,Polymer 是一套 polyfill 和 sugar 程式庫,可用於開發使用 Web Components 的應用程式。
認識 Yo、Grunt 和 Bower
Yeoman 是一個戴著帽子的男人,他有三種工具可以提高您的工作效率:
- yo 是鷹架工具,提供特定架構鷹架的生態系統,稱為產生器,可用於執行我先前提到的一些繁瑣工作。
- grunt 用於建構、預覽及測試專案,這要歸功於 Yeoman 團隊和 grunt-contrib 精心設計的工作。
- bower 用於依附元件管理,因此您不必再手動下載及管理指令碼。
只要一兩個指令,Yeoman 就能為應用程式 (或模型等個別部分) 編寫樣板程式碼、編譯 Sass、縮減及串連 CSS、JS、HTML 和圖片,並在目前目錄中啟動簡易網路伺服器。此外,還能執行單元測試等。
您可以從 Node Packaged Modules (npm) 安裝產生器,目前有超過 220 個產生器可供使用,其中許多是由開放原始碼社群編寫。熱門產生器包括 generator-angular、generator-backbone 和 generator-ember。
安裝最新版 Node.js 後,請開啟終端機並執行下列指令:
$ npm install -g yo
大功告成!現在您已擁有 Yo、Grunt 和 Bower,可以直接從指令列執行這些工具。執行 yo 的輸出內容如下:
Polymer 產生器
如前所述,Polymer 是 polyfill 和語法糖的程式庫,可讓您在新式瀏覽器中使用網頁元件。這項專案可讓開發人員使用未來的平台建構應用程式,並向 W3C 回報可進一步改善現行規格的地方。
generator-polymer 是新的產生器,可協助您使用 Yeoman 建構 Polymer 應用程式,讓您透過指令列輕鬆建立及自訂 Polymer (自訂) 元素,並使用 HTML 匯入功能匯入這些元素。這項功能會為您編寫樣板程式碼,節省寶貴時間。
接著,執行下列指令來安裝 Polymer 的產生器:
$ npm install generator-polymer -g
就這樣。現在,您的應用程式具備網頁元件的強大功能!
新安裝的產生器有幾個特定部分可供存取:
polymer:element可用於建構新的個別 Polymer 元素。例如:yo polymer:element carouselpolymer:app可用於建構初始 index.html、包含專案建構時設定的 Gruntfile.js,以及專案建議的 Grunt 工作和資料夾結構。您也可以選擇使用 Sass Bootstrap 設定專案樣式。
建構 Polymer 應用程式
我們將使用一些自訂 Polymer 元素和新的產生器,建構一個簡單的網誌。
首先,請前往終端機,建立新目錄,然後使用 mkdir my-new-project && cd $_ 進入該目錄。現在可以執行以下指令,啟動 Polymer 應用程式:
$ yo polymer
這會從 Bower 取得最新版本的 Polymer,並為工作流程建構 index.html、目錄結構和 Grunt 工作。應用程式正在前置準備,不妨先喝杯咖啡吧!
接下來,我們可以執行 grunt server 來預覽應用程式:
伺服器支援 LiveReload,也就是說,您可以啟動文字編輯器、編輯自訂元素,瀏覽器就會在儲存時重新載入。這樣一來,您就能即時查看應用程式目前的狀態。
接下來,我們要建立新的 Polymer 元素來代表網誌文章。
$ yo polymer:element post
Yeoman 會詢問我們幾個問題,例如是否要加入建構函式,或使用 HTML 匯入功能在 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>
其中包含:
- 自訂元素的樣板程式碼,可讓您在網頁中使用自訂 DOM 元素類型 (例如
<post-element>) - 用於「原生」用戶端範本的範本標記,以及用於封裝元素樣式的限定範圍樣式範例
- 元素註冊樣板和生命週期事件。
使用真實資料來源
網誌需要一個可以撰寫和閱讀新文章的地方。為了示範如何使用實際的資料服務,我們將使用 Google Apps Spreadsheets API。這樣一來,我們就能輕鬆讀取使用 Google 文件建立的任何試算表內容。
請按照下列步驟設定:
在瀏覽器 (建議使用 Chrome) 中開啟這份 Google 文件試算表。其中包含下列欄位的範例貼文資料:
- ID
- 標題
- 作者
- 內容
- 日期
- 關鍵字
- 電子郵件 (作者)
- Slug (貼文的 Slug 網址)
前往「檔案」選單,然後選取「建立副本」,建立自己的試算表副本。你可以隨意編輯內容,新增或移除貼文。
再次前往「檔案」選單,然後選取「發布到網路」。
按一下「開始發布」
在「取得已發布資料的連結」下方,從最後一個文字方塊複製所提供網址的金鑰部分。格式如下:https://docs.google.com/spreadsheet/ccc?key=0AhcraNy3sgspdDhuQ2pvN21JVW9NeVA0M1h4eGo3RGc#gid=0
將金鑰貼到下列網址中「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。
你可以將網址貼到瀏覽器中,然後前往該網址查看 JSON 版本的網誌內容。請記下網址,然後花點時間查看這項資料的格式,因為您稍後需要疊代這項資料,才能在畫面上顯示。
瀏覽器中的 JSON 輸出內容可能看起來有點複雜,但別擔心!我們只對貼文資料感興趣。
Google Spreadsheets API 會輸出網誌試算表中的每個欄位,並加上特殊前置字元 post.gsx$。例如:post.gsx$title.$t、post.gsx$author.$t、post.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。
取得公用程式後,您必須使用以下程式碼,將其匯入 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 ]] (如有提供)。
現在,為了填入目前的 [[route]],我們將使用名為 Flatiron director 的程式庫,每當網址雜湊變更時,該程式庫就會繫結至 [[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>
太棒了!我們現在有一個簡單的網誌,可從 JSON 讀取資料,並使用兩個以 Yeoman 建構的 Polymer 元素。
使用第三方元素
最近,網頁元件周圍的元素生態系統不斷成長,customelements.io 等元件庫網站也開始出現。瀏覽社群建立的元素後,我找到一個用於擷取 gravatar 個人資料的元素,我們也可以擷取並新增到網誌網站。
將 gravatar 元素來源複製到 /elements 目錄,透過 HTML 匯入功能將其納入 post.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>
來看看結果:
太棒了!
在相對短的時間內,我們建立了一個由多個網頁元件組成的簡單應用程式,不必擔心編寫樣板程式碼、手動下載依附元件,或設定本機伺服器或建構工作流程。
最佳化應用程式
Yeoman 工作流程包含另一個名為 Grunt 的開放原始碼專案,這項工作執行工具可執行多項建構專屬工作 (在 Gruntfile 中定義),產生最佳化版本的應用程式。單獨執行 grunt 會執行產生器設定的 default 工作,用於 lint 檢查、測試和建構:
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。
還有哪些精彩內容?
網頁元件仍在發展階段,因此相關工具也還在演進。
我們目前正在研究如何透過 Vulcanize (Polymer 專案的工具) 等專案串連 HTML 匯入,以提升載入效能,以及元件生態系統如何與 Bower 等套件管理工具搭配運作。
我們會在有更明確的答案時通知您,但未來還有許多令人期待的時刻。
使用 Bower 安裝 Polymer 獨立版
如果您想以較輕量的方式開始使用 Polymer,可以直接從 Bower 執行下列指令,安裝獨立版本:
bower install polymer
這會將其新增至 bower_components 目錄。接著,您可以在應用程式索引中手動參照該 ID,並在未來大放異彩。
結果如何?
您已瞭解如何使用 Yeoman 搭配網頁元件,建構 Polymer 應用程式。如果您對產生器有任何意見,請在留言中告訴我們、提交錯誤,或在 Yeoman Issue Tracker 中發布。我們很想知道,您是否希望生成器在其他方面有更好的表現,因為只有透過您的使用和意見回饋,我們才能改進 :)