إنشاء تطبيق الويب التقدّمي Google I/O 2016

منزل آيوا

ملخّص

تعرَّف على كيفية تطوير تطبيق من صفحة واحدة باستخدام مكونات الويب والبوليمر والتصميم المتعدد الأبعاد وإطلاقه في مرحلة الإنتاج على Google.com.

النتائج

  • معدّل تفاعل أكبر من التطبيق الأصلي (4:06 دقائق على الويب على الأجهزة الجوّالة مقابل 2:40 دقيقة من Android)
  • سرعة عرض أول 450 ملّي ثانية للمستخدمين العائدين من خلال التخزين المؤقت لدى عمال الخدمات
  • دعم 84٪ من الزوار مشغّلي الخدمات
  • وارتفعت نسبة حفظ "الإضافة إلى الشاشة الرئيسية" بنسبة +900% مقارنةً بالعام 2015.
  • انقطع اتصال 3.8% من المستخدمين بالإنترنت، مع استمرارهم في تحقيق 11 ألف مشاهدة للصفحة.
  • فعّل 50% من المستخدمين الذين سجّلوا الدخول الإشعارات.
  • تم إرسال 536 ألف إشعار إلى المستخدمين (%12 أعادوا إرسال الإشعارات).
  • يتيح 99% من متصفحات المستخدمين استخدام رموز polyfill لمكونات الويب.

نظرة عامة

هذا العام، سُررتُ بالعمل على تطبيق الويب التقدّمي Google I/O 2016، الذي يحمل اسم IOWA. فهو يناسب الأجهزة الجوّالة أولاً، ويعمل بلا اتصال بالإنترنت بالكامل، ويستند بشكل كبير إلى التصميم المتعدد الأبعاد.

IOWA هو تطبيق من صفحة واحدة (SPA) تم إنشاؤه باستخدام مكوّنات الويب والبوليمر وFirebase، ولديه خلفية شاملة مكتوبة في App Engine (Go). وهي تخزّن المحتوى مؤقتًا بشكل مسبق باستخدام مشغّل خدمات، كما تحمِّل الصفحات الجديدة ديناميكيًا وتنتقل بسلاسة بين طرق العرض، كما تعيد استخدام المحتوى بعد أول عملية تحميل.

في دراسة الحالة هذه، سأستعرض بعض القرارات المعمارية الأكثر إثارة للاهتمام التي اتخذناها للواجهة الأمامية. إذا كنت مهتمًا برمز المصدر، فراجعه على جيت هب.

العرض على GitHub

إنشاء SPA باستخدام مكوّنات الويب

كل صفحة كمكون

أحد الجوانب الأساسية في الواجهة الأمامية هو أنها تتمحور حول مكونات الويب. وفي الواقع، تعد كل صفحة في SPA مكونًا من مكوّنات ويب:

    <io-home-page date="2016-05-18T17:00:00Z" app="[[app]]"></io-home-page>
    <io-schedule-page date="2016-05-18T17:00:00Z" app="{ % templatetag openvariable % }app}}"></io-schedule-page>
    <io-attend-page></io-attend-page>
    <io-extended-page></io-extended-page>
    <io-faq-page></io-faq-page>

لماذا فعلنا ذلك؟ السبب الأول هو أن هذه التعليمة البرمجية سهلة القراءة. من الواضح تمامًا ماهية كل صفحة في تطبيقنا كقارئ لأول مرة. والسبب الثاني هو أن مكوّنات الويب تحتوي على بعض الخصائص الرائعة لإنشاء SPA. وتختفي الكثير من المشاكل الشائعة (إدارة الحالة وتفعيل طرق العرض وتحديد نطاق الأنماط) بفضل الميزات المضمّنة في العنصر <template> والعناصر المخصّصة وShadow DOM. وهذه هي أدوات المطوّرين المضمَّنة في المتصفّح. لماذا لا تستفيد منها؟

من خلال إنشاء عنصر مخصص لكل صفحة، حصلنا على الكثير مجانًا:

  • إدارة مراحل نشاط الصفحة
  • تحديد نطاق CSS/HTML الخاص بالصفحة.
  • يتم تجميع كل ملفات CSS/HTML/JS الخاصة بالصفحة وتحميلها معًا حسب الحاجة.
  • طرق العرض قابلة لإعادة الاستخدام. نظرًا لأن الصفحات هي عُقد DOM، فإن إضافتها أو إزالتها تؤدي إلى تغيير العرض.
  • فبإمكان مسؤولي الصيانة في المستقبل فهم تطبيقنا ببساطة من خلال تحريك الترميز.
  • يمكن تحسين الترميز الذي يعرضه الخادم تدريجيًا عند تسجيل تعريفات العناصر وترقيتها من خلال المتصفّح.
  • العناصر المخصصة لها نموذج وراث. الكود DRY هو تعليمة برمجية جيدة.
  • ...الكثير من الأشياء الأخرى.

لقد استفدنا إلى أقصى حد من هذه المزايا في IOWA. لنتعمق في بعض التفاصيل.

تفعيل الصفحات ديناميكيًا

العنصر <template> هو الطريقة العادية في المتصفّح لإنشاء ترميز قابل لإعادة الاستخدام. هناك خاصيتان يمكن لـ <template> الاستفادة منهما أولاً، أي شيء داخل <template> يكون غير صالح حتى يتم إنشاء مثيل للقالب. ثانيًا، يحلّل المتصفح الترميز ولكن لا يمكن الوصول إلى المحتوى من الصفحة الرئيسية. وهو عبارة عن جزء صحيح من الترميز وقابل لإعادة الاستخدام. مثال:

<template id="t">
    <div>This markup is inert and not part of the main page's DOM.</div>
    <img src="profile.png"> <!-- not loaded by the browser -->
    <video id="vid" src="vid.mp4" autoplay></video> <!-- doesn't load/start -->
    <script>alert("Not run until the template is stamped");</script>
</template>

يعمل البوليمر على extends <template> من خلال بعض العناصر المخصّصة لإضافة النوع، وهي <template is="dom-if"> و<template is="dom-repeat">. كلاهما عنصر مخصّص يوفّر إمكانيات إضافية في <template>. وبفضل الطبيعة التعريفية لمكوّنات الويب، يقدّم كلٌّ منهما ما تتوقعه بالضبط. يشير ذلك المصطلح إلى ترميز الطوابع في المكوّن الأول استنادًا إلى خيار شرطي. والنموذج الثاني يكرر الترميز لكل عنصر في القائمة (نموذج البيانات).

كيف يستخدم IOWA عناصر امتداد النوع هذه؟

إذا كنت تتذكر، فإن كل صفحة في IOWA هي مكون ويب. مع ذلك، سيكون من السخيف إعلان كل مكوّن عند التحميل الأول. وهذا يعني إنشاء مثيل لكل صفحة عند تحميل التطبيق لأول مرة. أردنا عدم التأثير سلبًا في أداء التحميل الأولي، خاصةً أنّ بعض المستخدمين لا ينتقلون إلّا إلى صفحة واحدة أو صفحتين.

كان حلنا هو الغش. في IOWA، نلتف عنصر كل صفحة في <template is="dom-if"> بحيث لا يتم تحميل محتواه عند أول تشغيل. بعد ذلك، يتم تفعيل الصفحات عندما تتطابق السمة name في النموذج مع عنوان URL. يتعامل مكوّن الويب <lazy-pages> مع كل هذا المنطق بالنسبة إلينا. يبدو الترميز على النحو التالي:

<!-- Lazy pages manages the template stamping. It watches for route changes
        and sets `template.if = true` on the appropriate template. -->
<lazy-pages>
    <template is="dom-if" name="home">
    <io-home-page date="2016-05-18T17:00:00Z"></io-home-page>
    </template>

    <template is="dom-if" name="schedule">
    <io-schedule-page date="2016-05-18T17:00:00Z"></io-schedule-page>
    </template>

    <template is="dom-if" name="attend">
    <io-attend-page></io-attend-page>
    </template>
</lazy-pages>

ما يعجبني في ذلك هو أنّ كل صفحة يتم تحليلها وتكون جاهزة للاستخدام عند تحميل الصفحة، ولكن لا يتم تنفيذ CSS/HTML/JS إلا عند الطلب (عندما يتم ختم <template> للصفحة الرئيسية). طرق العرض الديناميكية + الكسول باستخدام مكوّنات الويب FTW

التحسينات المستقبلية

عند تحميل الصفحة لأول مرة، يتم تحميل جميع عمليات استيراد HTML لكل صفحة دفعة واحدة. وقد يكون التحسين الواضح هو التحميل الكسول لتعريفات العناصر فقط عند الحاجة إليها. يحتوي البوليمر أيضًا على مساعد جيد لعمليات استيراد HTML غير المتزامنة:

Polymer.Base.importHref('io-home-page.html', (e) => { ... });

لا تفعل شركة IOWA ذلك لأننا أ) كنا كسول، وب) أنه ليس من الواضح مدى تعزيز الأداء الذي كنا سنشهده. كانت أول طلاءات لدينا حوالي ثانية واحدة.

إدارة مراحل نشاط الصفحة

تحدّد Custom Elements API "عمليات معاودة الاتصال بمراحل الحياة" لإدارة حالة المكوّن. عند تنفيذ هذه الطرق، فإنك تحصل على عناصر جذب مجانية في حياة أحد المكونات:

createdCallback() {
    // automatically called when an instance of the element is created.
}

attachedCallback() {
    // automatically called when the element is attached to the DOM.
}

detachedCallback() {
    // automatically called when the element is removed from the DOM.
}

attributeChangedCallback() {
    // automatically called when an HTML attribute changes.
}

كان من السهل الاستفادة من معاودة الاتصال هذه في IOWA. تذكر أن كل صفحة هي عقدة DOM مستقلة. الانتقال إلى "عرض جديد" في SPA يتعلق بإرفاق عقدة واحدة بـ DOM وإزالة عقدة أخرى.

استخدمنا attachedCallback لتنفيذ أعمال الإعداد (حالة البدء، وإرفاق أدوات معالجة الأحداث). عند انتقال المستخدمين إلى صفحة مختلفة، يتم حذف البيانات المتوفّرة في detachedCallback (إزالة أدوات الاستماع أو إعادة ضبط الحالة المشتركة). علاوةً على ذلك، وسّعنا أيضًا عمليات معاودة الاتصال ضمن مراحل نشاطها الأساسي من خلال إضافة العديد من عملياتنا الخاصة:

onPageTransitionDone() {
    // page transition animations are complete.
},

onSubpageTransitionDone() {
    // sub nav/tab page transitions are complete.
}

وكانت هذه الإضافات مفيدة لتأخير العمل وتقليل البيانات غير المحتملة بين عمليات انتقال الصفحات. سنتحدّث عن هذا الموضوع لاحقًا.

تجفيف الوظائف المشتركة عبر الصفحات

الاكتساب هو ميزة قوية في العناصر المخصّصة. يوفر نموذج توريث قياسي للويب.

للأسف، لم ينفِّذ الإصدار 1.0 من Polymer 1.0 بعد اكتساب العناصر في وقت كتابة هذا التقرير. في غضون ذلك، كانت ميزة السلوكيات البوليمرية مفيدة بنفس القدر. السلوكيات ما هي إلا مزيج.

وبدلاً من إنشاء واجهة برمجة التطبيقات نفسها على جميع الصفحات، كان من المنطقي تجفيف قاعدة الرموز من خلال إنشاء مزيج مشترك. على سبيل المثال، تحدّد PageBehavior الخصائص/الطرق الشائعة التي تحتاجها جميع الصفحات في تطبيقنا:

PageBehavior.html

let PageBehavior = {

    // Common properties all pages need.
    properties: {
    name: { type: String }, // Slug name of the page.
    ...
    },

    attached() {
    // If the page defines a `onPageTransitionDone`, call it when the router
    // fires 'page-transition-done'.
    if (this.onPageTransitionDone) {
        this.listen(document.body, 'page-transition-done', 'onPageTransitionDone');
    }

    // Update page meta data when new page is navigated to.
    document.body.id = `page-${this.name}`;
    document.title = this.title || 'Google I/O 2016';

    // Scroll to top of new page.
    if (IOWA.Elements.Scroller) {
        IOWA.Elements.Scroller.scrollTop = 0;
    }

    this.setupSubnavEffects();
    },

    detached() {
    this.unlisten(document.body, 'page-transition-done', 'onPageTransitionDone');
    this.teardownSubnavEffects();
    }
};

IOWA.IOBehaviors = IOWA.IOBehaviors || {PageBehavior: PageBehavior};

كما ترى، ينفّذ PageBehavior المهام الشائعة التي يتم تشغيلها عند زيارة صفحة جديدة. إجراءات مثل تعديل document.title وإعادة ضبط موضع التمرير وإعداد أدوات معالجة الأحداث لتأثيرات التمرير والتنقل الفرعي.

تستخدم الصفحات الفردية PageBehavior عن طريق تحميلها كتبعية واستخدام behaviors. كما أنها لها حرية إلغاء خصائصها/طرقها الأساسية إذا لزم الأمر. على سبيل المثال، إليك ما تلغيه "الفئة الفرعية" في صفحتنا الرئيسية:

io-home-page.html

<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="PageBehavior.html">
<!-- rest of the import dependencies used by the page. -->

<dom-module id="io-home-page">
    <template>
    <!-- PAGE'S MARKUP -->
    </template>
    <script>
    Polymer({
        is: 'io-home-page',

        behaviors: [IOBehaviors.PageBehavior], // All pages have common functionality.

        // Pages define their own title and slug for the router.
        title: 'Schedule - Google I/O 2016',
        name: 'home',

        // The home page has custom setup work when it's added navigated to.
        // Note: PageBehavior's attached also gets called.
        attached() {
        if (this.app.isPhoneSize) {
            this.listen(IOWA.Elements.ScrollContainer, 'scroll', '_onPageScroll');
        }
        },

        // The home page does its own cleanup when a new page is navigated to.
        // Note: PageBehavior's detached also gets called.
        detached() {
        this.unlisten(IOWA.Elements.ScrollContainer, 'scroll', '_onPageScroll');
        },

        // The home page can define onPageTransitionDone to do extra work
        // when page transitions are done, and thus preventing janky animations.
        onPageTransitionDone() {
        ...
        }
    });
    </script>
</dom-module>

أنماط المشاركة

لمشاركة الأنماط من خلال المكوّنات المختلفة في التطبيق، استخدمنا وحدات الأنماط المشتركة من البوليمر. تتيح لك وحدات النمط تحديد جزء من CSS مرة واحدة وإعادة استخدامه في أماكن مختلفة في التطبيق. فبالنسبة إلينا، تعني "الأماكن المختلفة" مكونات مختلفة.

في IOWA، أنشأنا shared-app-styles لمشاركة الألوان وأسلوب الخط وفئات التنسيق على مستوى الصفحات والمكوّنات الأخرى التي أنشأناها.

shared-app-styles.html

<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../bower_components/iron-flex-layout/iron-flex-layout.html">
<link rel="import" href="../bower_components/paper-styles/color.html">

<dom-module id="shared-app-styles">
    <template>
    <style>
        [layout] {
        @apply(--layout);
        }
        [layout][horizontal] {
        @apply(--layout-horizontal);
        }
        .scrollable {
        @apply(--layout-scroll);
        }
        .noscroll {
        overflow: hidden;
        }
        /* Style radio buttons and tabs the same throughout the app */
        paper-tabs {
        --paper-tabs-selection-bar-color: currentcolor;
        }
        paper-radio-button {
        --paper-radio-button-checked-color: var(--paper-cyan-600);
        --paper-radio-button-checked-ink-color: var(--paper-cyan-600);
        }
        ...
    </style>
    </template>
</dom-module>

io-home-page.html

<link rel="import" href="shared-app-styles.html">
<!-- Rest of import dependencies used by the page. -->

<dom-module id="io-home-page">
    <template>
    <style include="shared-app-styles">
        :host { display: block} /* Other element styles can go here. */
    </style>
    <!-- PAGE'S MARKUP -->
    </template>
    <script>Polymer({...});</script>
</dom-module>

هنا، <style include="shared-app-styles"></style> هي بنية البوليمر للقول "تضمين الأنماط في الوحدة المسماة "أنماط التطبيقات المشتركة".

مشاركة حالة التطبيق

أنت تعلم الآن أن كل صفحة في تطبيقنا هي عنصر مخصص. لقد قلتها مليون مرة. ولكن إذا كانت كل صفحة عبارة عن مكوّن ويب مستقل، قد تتساءل عن كيفية مشاركة الحالة عبر التطبيق.

يستخدم IOWA تقنية تشبه حقن التبعية (الزاوي) أو Redux (التفاعل) لحالة المشاركة. لقد أنشأنا موقعًا عالميًا app وعلّقنا مواقع فرعية مشتركة منه. يجتاز app تطبيقنا عن طريق إدخاله في كل مكون يحتاج إلى بياناته. ويسهِّل استخدام ميزات ربط البيانات المتوفّرة في البوليمر هذه العملية لأنّه يمكننا توصيل الأسلاك بدون كتابة أي رمز:

<lazy-pages>
    <template is="dom-if" name="home">
    <io-home-page date="2016-05-18T17:00:00Z" app="[[app]]"></io-home-page>
    </template>

    <template is="dom-if" name="schedule">
    <io-schedule-page date="2016-05-18T17:00:00Z" app="{ % templatetag openvariable % }app}}"></io-schedule-page>
    </template>
    ...
</lazy-pages>

<google-signin client-id="..." scopes="profile email"
                            user="{ % templatetag openvariable % }app.currentUser}}"></google-signin>

<iron-media-query query="(min-width:320px) and (max-width:768px)"
                                query-matches="{ % templatetag openvariable % }app.isPhoneSize}}"></iron-media-query>

يعدِّل العنصر <google-signin> السمة user عند تسجيل المستخدمين الدخول إلى تطبيقنا. وبما أنّ هذه السمة مرتبطة بـ app.currentUser، فإنّ أي صفحة تريد الوصول إلى المستخدم الحالي تحتاج إليها للربط بـ app وقراءة الموقع الفرعي currentUser. وهذه التقنية مفيدة في حد ذاتها لمشاركة الحالة في التطبيق، إلا أنّ الفائدة الأخرى هي أننا أنشأنا عنصر تسجيل الدخول الأحادي وإعادة استخدام نتائجه على جميع أقسام الموقع الإلكتروني. الأمر نفسه بالنسبة للاستعلامات عن الوسائط. في كل صفحة، كان من الفاقد إنشاء نسخة طبق الأصل من تسجيل الدخول أو إنشاء مجموعتها الخاصة من الاستعلامات عن الوسائط. بدلاً من ذلك، تتوفّر المكوّنات المسؤولة عن الوظائف/البيانات على مستوى التطبيق على مستوى التطبيق.

عمليات انتقال الصفحة

أثناء التنقّل في تطبيق الويب Google I/O، ستلاحظ انتقالات سلسة في الصفحة (à la Material Design).

عمليات الانتقال بين صفحات IOWA.
عمليات الانتقال بين صفحة IOWA.

عندما ينتقل المستخدمون إلى صفحة جديدة، يحدث سلسلة من الأشياء:

  1. يعمل شريط التنقل العلوي على تمرير شريط التحديد إلى الرابط الجديد.
  2. يتلاشى عنوان الصفحة.
  3. يتم تمرير محتوى الصفحة للأسفل ثم يتلاشى.
  4. وبعكس هذه الصور المتحركة، يظهر عنوان الصفحة الجديدة ومحتواها.
  5. (اختياري) تقوم الصفحة الجديدة بإجراءات إعداد إضافية.

من بين التحديات التي واجهتنا هي محاولة صياغة هذا الانتقال السلس بدون التضحية بالأداء. هناك الكثير من العمل الديناميكي الذي يحدث، ونحن نرحب بـ الإخفاقات في حفلتنا. ارتكز الحلّ الذي نقدمه على واجهة برمجة التطبيقات Web Animations API والوعود. أتاح لنا استخدام الاثنين معًا الاستفادة من ميزات متنوّعة، وتوفير نظام للصور المتحركة للتوصيل والتشغيل، والتحكّم الدقيق لتقليل البيانات غير النشطة das.

طريقة العمل

عندما ينقر المستخدمون للانتقال إلى صفحة جديدة (أو ينتقلون إلى الأمام أو الخلف)، تؤدي ميزة runPageTransition() في جهاز التوجيه أفضل أداء من خلال الاطّلاع على سلسلة من التعهدات. أتاح لنا استخدام التعهدات تنسيق الصور المتحركة بعناية وساعد على ترسيخ "عدم المزامنة" للصور المتحركة في CSS والتحميل الديناميكي للمحتوى.

class Router {

    init() {
    window.addEventListener('popstate', e => this.runPageTransition());
    }

    runPageTransition() {
    let endPage = this.state.end.page;

    this.fire('page-transition-start');              // 1. Let current page know it's starting.

    IOWA.PageAnimation.runExitAnimation()            // 2. Play exist animation sequence.
        .then(() => {
        IOWA.Elements.LazyPages.selected = endPage;  // 3. Activate new page in <lazy-pages>.
        this.state.current = this.parseUrl(this.state.end.href);
        })
        .then(() => IOWA.PageAnimation.runEnterAnimation())  // 4. Play entry animation sequence.
        .then(() => this.fire('page-transition-done')) // 5. Tell new page transitions are done.
        .catch(e => IOWA.Util.reportError(e));
    }

}

استدعاء من القسم "إبقاء الأمور DRY: وظائف شائعة في مختلف الصفحات"، تستمع الصفحات إلى حدثَي page-transition-start وpage-transition-done في نموذج العناصر في المستند (DOM). يمكنك الآن الاطّلاع على أماكن تنشيط هذه الأحداث.

استخدمنا واجهة برمجة التطبيقات Web Animations API بدلاً من أدوات المساعدة التي يبلغ عددها runEnterAnimation من أصل runExitAnimation. في حال استخدام runExitAnimation، نلتقط عُقدتَين من عُقد DOM (إعلان التسمية الرئيسية ومنطقة المحتوى الرئيسية)، ثم نحدِّد بداية ونهاية كل رسم متحرك، وننشئ GroupEffect لتشغيل العُقدتين بالتوازي:

function runExitAnimation(section) {
    let main = section.querySelector('.slide-up');
    let masthead = section.querySelector('.masthead');

    let start = {transform: 'translate(0,0)', opacity: 1};
    let end = {transform: 'translate(0,-100px)', opacity: 0};
    let opts = {duration: 400, easing: 'cubic-bezier(.4, 0, .2, 1)'};
    let opts_delay = {duration: 400, delay: 200};

    return new GroupEffect([
    new KeyframeEffect(masthead, [start, end], opts),
    new KeyframeEffect(main, [{opacity: 1}, {opacity: 0}], opts_delay)
    ]);
}

ما عليك سوى تعديل الصفيف لجعل انتقالات العرض أكثر (أو أقل) أكثر تفصيلاً!

تأثيرات التمرير

يحتوي IOWA على بعض التأثيرات الشيقة عند التمرير في الصفحة. الأول هو زر الإجراء العائم (FAB) الذي يعيد المستخدمين إلى أعلى الصفحة:

    <a href="#" tabindex="-1" aria-hidden="true" aria-label="back to top" onclick="backToTop">
      <paper-fab icon="io:expand-less" noink tabindex="-1"></paper-fab>
    </a>

ويتم تنفيذ التمرير السلس باستخدام عناصر تنسيق التطبيقات من البوليمر. توفر تأثيرات تمرير خارج الصندوق مثل التنقلات العلوية الثابتة/العودة، والظلال الخلفية، وانتقالات الألوان والخلفية، وتأثيرات المنظر، والتمرير السلس.

    // Smooth scrolling the back to top FAB.
    function backToTop(e) {
      e.preventDefault();

      Polymer.AppLayout.scroll({top: 0, behavior: 'smooth',
                                target: document.documentElement});

      e.target.blur();  // Kick focus back to the page so user starts from the top of the doc.
    }

هناك مكان آخر استخدمنا فيه عناصر <app-layout> وهو التنقل الثابت. ويختفي هذا الفيديو كما ترى في الفيديو عندما يمرّر المستخدمون لأسفل الصفحة ويعودون إليها عند التمرير للأعلى مجددًا.

تنقلات التمرير الثابت
تنقلات التنقل الثابتة باستخدام .

لقد استخدمنا العنصر <app-header> كما هو إلى حدّ كبير. وكان من السهل إدراجه والحصول على تأثيرات تمرير رائعة في التطبيق. بالتأكيد، كان بإمكاننا تنفيذها بأنفسنا، ولكن وفّرنا التفاصيل في مُكوِّن قابل لإعادة الاستخدام لتوفير الكثير من الوقت.

عرّف العنصر. ويمكنك تخصيصه باستخدام السمات. لقد أنهيت عملك!

    <app-header reveals condenses effects="fade-background waterfall"></app-header>

الخلاصة

بالنسبة إلى تطبيق الويب التقدّمي I/O، تمكّنا من إنشاء واجهة أمامية كاملة خلال عدة أسابيع بفضل مكوّنات الويب وأدوات التصميم المتعدد الأبعاد المعدّة مسبقًا من البوليمر. وتتوافق ميزات واجهات برمجة التطبيقات الأصلية (العناصر المخصّصة وShadow DOM و<template>) مع ديناميكية SPA بشكل طبيعي. توفر قابلية إعادة الاستخدام الكثير من الوقت.

إذا كنت مهتمًا بإنشاء تطبيق ويب تقدّمي بنفسك، اطّلِع على مجموعة أدوات التطبيقات. مجموعة أدوات تطبيق Polymer عبارة عن مجموعة من المكونات والأدوات والنماذج لإنشاء تطبيقات الويب التقدّمية (PWA) باستخدام البوليمر. إنها طريقة سهلة لبدء الاستخدام.