יצירת תבניות לאפליקציות אינטרנט באמצעות כלים מודרניים
מבוא
שלום שלום. כל מי שכותב אפליקציית אינטרנט יודע כמה חשוב לשמור על פרודוקטיביות. קשה מאוד לבצע משימות מייגעות כמו חיפוש תבנית ה-boilerplate המתאימה, הגדרת תהליך פיתוח ובדיקה, וקיצור וקידוד של כל המקורות.
למרבה המזל, כלי חזית מודרניים יכולים לעזור להפוך את התהליך הזה לאוטומטי, כך שתוכלו להתמקד בכתיבת אפליקציה מעולה. במאמר הזה נסביר איך להשתמש ב-Yeoman, תהליך עבודה של כלים לאפליקציות אינטרנט שמאפשר לייעל את יצירת האפליקציות באמצעות Polymer, ספרייה של polyfills ו-sugar לפיתוח אפליקציות באמצעות רכיבי אינטרנט.
נעים להכיר: 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 Generator
כפי שציינתי קודם, Polymer היא ספרייה של רכיבי Polyfill ו-sugar שמאפשרת להשתמש ברכיבי אינטרנט בדפדפנים מודרניים. הפרויקט מאפשר למפתחים ליצור אפליקציות באמצעות הפלטפורמה של העתיד, ולעדכן את W3C לגבי מקומות שבהם אפשר לשפר עוד יותר את המפרטים בזמן אמת.
generator-polymer הוא גנרטור חדש שעוזר ליצור תבניות לאפליקציות Polymer באמצעות Yeoman. הכלי מאפשר ליצור ולשנות בקלות רכיבים (מותאמים אישית) של Polymer באמצעות שורת הפקודה, ולייבא אותם באמצעות HTML Imports. כך תוכלו לחסוך זמן כי המערכת כותבת בשבילכם את קוד ה-boilerplate.
בשלב הבא, מתקינים את ה-generator של Polymer באמצעות הפקודה:
$ npm install generator-polymer -g
זה הכול. עכשיו לאפליקציה יש כוחות-על של רכיבי אינטרנט!
לגנרטור החדש שהותקן יש כמה תכונות ספציפיות שתהיה לכם גישה אליהן:
polymer:element
משמש ליצירת תבנית לרכיבי Polymer חדשים. לדוגמה:yo polymer:element carousel
polymer:app
משמש ליצירת תבנית (scaffold) של קובץ index.html הראשוני, קובץ Gruntfile.js שמכיל הגדרות לזמן ה-build של הפרויקט, וגם משימות Grunt ומבנה תיקיות מומלץ לפרויקט. בנוסף, תוכלו להשתמש ב-Sass Bootstrap לסגנונות של הפרויקט.
פיתוח אפליקציה ב-Polymer
אנחנו הולכים ליצור בלוג פשוט באמצעות כמה רכיבי Polymer מותאמים אישית והגנרטור החדש שלנו.
כדי להתחיל, עוברים לטרמינל, יוצרים ספרייה חדשה ומשתמשים ב-cd כדי לעבור אליה באמצעות mkdir my-new-project && cd $_
. עכשיו אפשר להתחיל לעבוד על אפליקציית Polymer על ידי הפעלת הפקודה הבאה:
$ yo polymer
הפקודה הזו מקבלת את הגרסה האחרונה של Polymer מ-Bower, ויוצרת תשתיות של 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
הפקודה הזו יוצרת רכיב Polymer חדש בספרייה /elements
בשם 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>
הוא מכיל:
- קוד תבנית לרכיב בהתאמה אישית, שמאפשר לכם להשתמש בסוג רכיב DOM בהתאמה אישית בדף (למשל
<post-element>
) - תג תבנית ליצירת תבניות 'מקומיות' בצד הלקוח, ודוגמאות לסגנונות ברמת ההיקף לסינכרון הסגנונות של הרכיב
- תבנית רישום של רכיבים ואירועי מחזור חיים.
עבודה עם מקור נתונים אמיתי
לבלוג שלנו יהיה צורך במקום שבו נוכל לכתוב ולקרוא פוסטים חדשים. כדי להדגים את העבודה עם שירות נתונים אמיתי, נשתמש ב-Google Apps Spreadsheets API. כך אנחנו יכולים לקרוא בקלות את התוכן של כל גיליון אלקטרוני שנוצר באמצעות Google Docs.
נתחיל בהגדרה:
בדפדפן (מומלץ להשתמש ב-Chrome בשלבים האלה), פותחים את הגיליון האלקטרוני הזה ב-Google Docs. הוא מכיל נתוני פוסט לדוגמה בשדות הבאים:
- מזהה
- כותרת
- מחבר/ת
- תוכן
- תאריך
- מילות מפתח
- כתובת אימייל (של המחבר)
- Slug (לכתובת ה-URL של ה-Slug של הפוסט)
עוברים לתפריט קובץ ובוחרים באפשרות יצירת עותק כדי ליצור עותק משלכם של הגיליון האלקטרוני. אתם יכולים לערוך את התוכן בזמן הפנוי שלכם, להוסיף פוסטים או להסיר אותם.
חוזרים לתפריט קובץ ובוחרים באפשרות פרסום באינטרנט.
לוחצים על התחלת הפרסום.
בקטע קבלת קישור לנתונים שפורסמו, מעתיקים את החלק key (מפתח) מכתובת ה-URL שצוינה בתיבת הטקסט האחרונה. כך הוא נראה: https://docs.google.com/spreadsheet/ccc?key=0AhcraNy3sgspdDhuQ2pvN21JVW9NeVA0M1h4eGo3RGc#gid=0
מדביקים את המפתח בכתובת ה-URL הבאה, במקום 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.
אפשר להדביק את כתובת ה-URL בדפדפן ולנווט אליה כדי להציג את גרסת ה-JSON של תוכן הבלוג. שימו לב לכתובת ה-URL, ואז הקדישו קצת זמן לבדיקה של הפורמט של הנתונים האלה, כי תצטרכו לעבור עליהם כדי להציג אותם במסך מאוחר יותר.
פלט ה-JSON בדפדפן עשוי להיראות קצת מרתיע, אבל אל דאגה. אנחנו מעוניינים רק בנתונים של הפוסטים שלך.
ממשק Google Sheets API מנפיק את כל השדות בגיליון האלקטרוני של הבלוג עם קידומת מיוחדת post.gsx$
. לדוגמה: post.gsx$title.$t
, post.gsx$author.$t
, post.gsx$content.$t
וכו'. כשנעבור על כל 'שורה' בפלט ה-JSON, נפנה לשדות האלה כדי לקבל את הערכים הרלוונטיים לכל פוסט.
עכשיו אפשר לערוך את רכיב הפוסט החדש שנוצר כדי לקשר קטעי תגי עיצוב לנתונים בגיליון האלקטרוני. כדי לעשות זאת, אנחנו משיקים את המאפיין post
, שיקריא את שם הפוסט, המחבר, התוכן ושדות אחרים שיצרנו מקודם. המאפיין selected
(שמאכלסים מאוחר יותר) משמש להצגת פוסט רק אם משתמש מנווט אל ה-slug הנכון שלו.
<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
הפעם מייבאים את הבלוג לקובץ index.html באמצעות ייבוא קובצי 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. אפשר לקבל את המתאם על ידי יצירת עותקים (cloning) של המאגר ב-git, או על ידי התקנה של polymer-elements
דרך Bower באמצעות הפקודה bower install 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>
עכשיו אפשר להוסיף תבניות כדי לבצע איטרציה על הגיליון האלקטרוני אחרי שהוא נקרא. הפקודה הראשונה יוצרת טבלת תוכן, עם כותרת מקושרת של פוסט שמצביעה על ה-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>
השני יוצר מופע אחד של 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]] בכל פעם שגיבוב כתובת ה-URL משתנה.
למרבה המזל, יש רכיב 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 ומשתמש בשני רכיבי Polymer שנוצרו באמצעות Yeoman.
עבודה עם רכיבים של צד שלישי
לאחרונה, הסביבה העסקית של רכיבים ב-Web Components הולכת וגדלה, ויש יותר ויותר אתרים של גלריות רכיבים כמו 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>
בואו נראה מה זה נותן לנו:
יפה!
תוך זמן קצר יחסית, יצרנו אפליקציה פשוטה המורכבת מכמה רכיבי אינטרנט, בלי לדאוג לכתוב קוד סטנדרטי, להוריד באופן ידני יחסי תלות או להגדיר שרת מקומי או תהליך עבודה ל-build.
אופטימיזציה של האפליקציה
תהליך העבודה של Yeoman כולל פרויקט נוסף בקוד פתוח שנקרא Grunt – כלי להרצת משימות שיכול להריץ מספר משימות ספציפיות ל-build (שמוגדרות בקובץ Gruntfile) כדי ליצור גרסה אופטימיזציה של האפליקציה. הפעלת grunt
בפני עצמה תבצע משימה default
שהגנרטור הגדיר לצורך איתור שגיאות בקוד, בדיקה ויצירת גרסאות build:
grunt.registerTask('default', [
'jshint',
'test',
'build'
]);
המשימה jshint
שלמעלה תבדוק את קובץ ה-.jshintrc
כדי ללמוד את ההעדפות שלכם, ולאחר מכן תריץ אותו בכל קובצי ה-JavaScript בפרויקט. במסמכי העזרה מפורטות כל האפשרויות של JSHint.
המשימה test
נראית בערך כך, והיא יכולה ליצור ולהציג את האפליקציה שלכם במסגרת הבדיקה שאנחנו ממליצים עליה מראש, Mocha. הוא גם יבצע את הבדיקות בשבילכם:
grunt.registerTask('test', [
'clean:server',
'createDefaultTemplate',
'jst',
'compass',
'connect:test',
'mocha'
]);
מכיוון שהאפליקציה שלנו במקרה הזה פשוטה למדי, נשאיר את כתיבת הבדיקות לכם כתרגיל נפרד. יש עוד כמה דברים שצריך לטפל בהם בתהליך ה-build, אז נראה מה המשימה grunt build
שתוגדר ב-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
]);
מריצים את grunt build
וצריך להיבנות גרסה של האפליקציה מוכנה לייצור, שאפשר לשלוח. ננסה.
הצלחת!
אם תיתקלו בבעיה, תוכלו להיעזר בגרסה מוכנה מראש של polymer-blog בכתובת https://github.com/addyosmani/polymer-blog.
מה עוד יש לנו בחנות?
רכיבי ה-Web עדיין נמצאים בתהליך התפתחות, וכך גם הכלים שקשורים אליהם.
אנחנו בודקים כרגע איך אפשר לשרשר את ייבוא ה-HTML כדי לשפר את ביצועי הטעינה באמצעות פרויקטים כמו Vulcanize (כלי של פרויקט Polymer), ואיך הסביבה העסקית של הרכיבים עשויה לפעול עם מנהל חבילות כמו Bower.
נעדכן אתכם כשיהיו לנו תשובות טובות יותר לשאלות האלה, אבל יש עוד הרבה דברים מעניינים שצפויים לקרות.
התקנה של Polymer בנפרד באמצעות Bower
אם אתם מעדיפים להתחיל עם גרסת מינימום של Polymer, תוכלו להתקין אותה ישירות מ-Bower כגרסה עצמאית:
bower install polymer
הפקודה הזו תוסיף אותו לספרייה bower_components. לאחר מכן תוכלו להפנות אליו באופן ידני באינדקס האפליקציות ולשדרג את העתיד.
מה דעתך?
עכשיו אתם יודעים איך ליצור תבנית לאפליקציית Polymer באמצעות רכיבי אינטרנט עם Yeoman. יש לכם משוב על הגנרטור? נשמח לשמוע אותו בתגובות, לדווח על באג או לפרסם אותו במערכת למעקב אחר בעיות ב-Yeoman. נשמח לדעת אם יש משהו נוסף שרצית שהגנרטור יעשה טוב יותר, כי רק באמצעות השימוש שלך והמשוב שלך נוכל לשפר אותו :)