Laten we webcomponenten bouwen! Deel 4:Polymeerbibliotheek

Op componenten gebaseerde gebruikersinterface is tegenwoordig een rage. Wist u dat het web zijn eigen native componentmodule heeft waarvoor geen bibliotheken nodig zijn? Waargebeurd verhaal! U kunt componenten uit één bestand schrijven, publiceren en hergebruiken die in elke* goede browser en in elk framework werken (als dat uw ding is).

In ons laatste bericht hebben we geleerd hoe we componenten uit één bestand kunnen schrijven met niets anders dan JavaScript en de DOM API.

Vandaag duiken we in de originele bibliotheek met webcomponenten:Polymer. We refactoren de <lazy-image> onderdeel dat we de vorige keer hebben gebouwd om te profiteren van de handige functies van Polymer. We zullen ook leren hoe u volledige apps kunt samenstellen uit op Polymeer gebaseerde componenten met behulp van hun expressieve sjabloonsysteem en tweerichtingsbinding. We zullen enkele van de fantastische kant-en-klare papieren elementen bekijken die door het Polymer-team zijn gepubliceerd. En als laatste zullen we enkele van de handige tools van het Polymer-project bekijken en leren hoe ze nuttig zijn voor elk webcomponentproject, niet alleen voor Polymer-apps.

  • Het polymeerproject
  • Refactoring <lazy-image>
    • Eigenschappen
    • Sjablonen voor gegevensbinding
  • Meer polymeerfuncties
    • Geavanceerde gegevensbinding
    • Waarnemers en berekende eigenschappen
    • Eigenschapsbeschrijvingen
    • Helper-elementen
  • Polymeer-apps samenstellen
  • Papierelementen
  • Polymeergereedschap
    • prpl-server
    • Polymeer CLI
    • WebComponents.org

Het polymeerproject

Het Polymer Project begon al in 2012/2013 met als doel de mogelijkheden van het webplatform te verbeteren. Volgens de legende belegde een groep Chrome-browseringenieurs diep in de ingewanden van Googleplex een geheime seance met een groep webontwikkelaars om de toekomstige koers van het web in het algemeen in kaart te brengen.

De browseringenieurs vroegen de webontwikkelaars om hen te vertellen hoe ze wilden dat webontwikkeling er over vijf jaar uit zou zien, en toen begonnen ze het te bouwen. Het resultaat was de eerste release van de Polymer-bibliotheek en het begin van het moderne verhaal over webcomponenten.

Sindsdien is het Polymer Project rond, zodat het nu mogelijk is om webcomponenten te schrijven zonder Polymer Library te gebruiken. Maar het Polymer Project is nog steeds springlevend. Ze onderhouden verschillende voorstellen voor webplatforms en pleiten voor een meer op standaarden gebaseerde vorm van webontwikkeling dan momenteel populair is.

De Polymer-bibliotheek daarentegen is sindsdien slechts een van een aantal alternatieven geworden voor het factoriseren van webcomponenten en op componenten gebaseerde apps.

Verwar die twee dingen dus niet. Het Project gaat over het platform als geheel, de Bibliotheek gaat over het helpen van u bij het bouwen van componenten.

Refactoring <lazy-image>

Dus laten we erin duiken! En aangezien we onze <lazy-image> . al hebben ontwikkeld vanille-component, laten we het als basis gebruiken om ook Polymer te verkennen.

Onze eerste stap in het herstructureren van <lazy-image> zal zijn om de Polymer-bibliotheek te installeren en te importeren.

npm i -S @polymer/polymer

We zullen onze component ook een beetje hernoemen om ons te helpen ons hoofd recht te houden:

import { PolymerElement, html } from '@polymer/polymer'

const tagName = 'polymer-lazy-image';

class PolymerLazyImage extends PolymerElement {
  /* ... */
}

customElements.define(tagName, PolymerLazyImage)

Polymer 3.0 en de papieren elementen vereisen dat we een transformatie toepassen op alle modulespecificaties, hetzij in een bouwstap, hetzij als een runtime-ding van de server. We gebruiken polymer serve , die voor ons de naakte bestekschrijvers in een oogwenk transformeert.

npm i -D polymer-cli
npx polymer serve

Een andere belangrijke stap die we nu moeten nemen voordat we nog meer gaan rommelen, is door de super . te bellen versies van al onze levenscyclus-callbacks.

connectedCallback() {
  super.connectedCallback();
  // ...
}

disconnectedCallback() {
  super.disconnectedCallback();
  // ...
}

Als u dit niet doet, ontstaan ​​er problemen, aangezien The PolymerElement basisklasse moet werk doen als er dingen in de levenscyclus gebeuren. Werk zoals het afhandelen van de polyfills, wat we niet meer handmatig hoeven te doen...

connectedCallback() {
  super.connectedCallback();
  this.setAttribute('role', 'presentation');
  if ('IntersectionObserver' in window) this.initIntersectionObserver();
  else this.intersecting = true;
}

We kunnen alle shadowRoot . kwijtraken - en ShadyCSS -gerelateerde code nu, inclusief updateShadyStyles , want Polymer regelt dat voor ons. Mooi hoor! Het werken met bibliotheken heeft één stress - het ondersteunen van de polyfills - uit ons hoofd gehaald.

Eigenschappen

Met Polymer kun je de eigenschappen van je element statisch declareren, en ik bedoel 'statisch' in de zin van beide static get en 'op het moment van schrijven'. Wanneer u een eigenschap in dat blok declareert, regelt Polymer de synchronisatie van attributen en eigenschappen voor u. Dat betekent dat wanneer de src attribuut op ons element is ingesteld, zal Polymer automatisch de src . updaten eigenschap op de elementinstantie.

Dus nu kunnen we onze attributeChangedCallback . verwijderen , safeSetAttribute , en al onze getters en setters, en vervang ze door een statische eigenschappenkaart met enkele speciale polymeerspecifieke descriptors.

static get properties() {
  return {
    /** Image alt-text. */
    alt: String,

    /**
     * Whether the element is on screen.
     * @type {Boolean}
     */
    intersecting: {
      type: Boolean,
      reflectToAttribute: true,
      notify: true,
    },

    /** Image URI. */
    src: String,
  };
}

Polymeer bindt standaard aan eigenschappen, niet aan attributen. Dit betekent dat als u zich bindt aan een van de eigenschappen van uw element in de polymeersjabloon van een hostelement, dit niet noodzakelijkerwijs als een attribuut op het element wordt weergegeven. De reflectToAttribute instellen boolean op een eigenschapsdescriptor zorgt ervoor dat wanneer de eigenschap verandert, Polymer ook het juiste attribuut voor het element instelt. Maak je echter geen zorgen, zelfs als je een eigenschap declareert met een constructor zoals propName: String , zullen kenmerkwijzigingen altijd de bijbehorende eigenschap bijwerken, ongeacht of u reflectToAttribute instelt of niet .

Opmerking :Polymer transformeert camelCase-eigenschapsnamen naar dash-case attribuutnamen en vice versa. Dit is trouwens de reden waarom de Polymer-bibliotheek sommige van de 'Custom Elements Everywhere'-tests niet doorstaat.

De notify boolean zorgt ervoor dat uw element elke keer dat uw eigendom verandert, een aangepaste gebeurtenis verzendt. Het evenement heet property-name-changed bijv. intersecting-changed voor de intersecting eigendom, en zal hebben zoals het is detail eigenschap een object met de sleutel value dat wijst op de nieuwwaarde van uw eigendom.

lazyImage.addEventListener('intersecting-changed', event => {
  console.log(event.detail.value) // value of 'intersecting';
})

Dit is de basis van Polymer's tweerichtingsbindsysteem. Het is hier niet strikt noodzakelijk, maar we kunnen die gebeurtenissen net zo goed blootleggen, voor het geval een gebruiker de intersecting van een afbeelding wil binden. status omhoog naar een omsluitend onderdeel.

Dus nu kunnen we ook de setIntersecting . verwijderen methode, want met behulp van onze eigendomskaart en het sjabloonsysteem van Polymer hebben we het niet nodig.

We hebben later meer over de eigenschapbeschrijvingen van Polymer.

Sjablonen voor gegevensbinding

We definiëren de sjablonen van een Polymer 3-element met een statische template getter die een getagde letterlijke sjabloon retourneert.

static get template() {
  return html`
    I'm the Template!
  `;
}

Polymeersjablonen hebben een speciale syntaxis die doet denken aan een stuur of snor. Eenzijdige (data-omlaag) bindingen worden gemaakt met dubbele [[vierkante haken]], en tweerichtings (data-omhoog) bindingen worden gemaakt met dubbele-{{ accolades}} .

<some-input input="{{myInput}}"></some-input>

<some-element
    some-property="[[myInput]]"
    some-attribute$="[[myAttribute]]"
></some-element>

In dit voorbeeld, wanneer <some-input> vuurt een input-changed . af gebeurtenis, werkt het host-element de someProperty . bij eigendom op <some-element> . In JS-termen is het een eenvoudige opdracht:someElementInstance.someProperty = this.myInput .

Als u zich wilt binden aan een kenmerk in plaats van aan een eigenschap, voegt u de $ . toe teken aan de binding:wanneer myOtherProp wijzigingen, de some-attribute op <some-element> zal updaten:someElementInstance.setAttribute('some-attribute', this.myOtherProp) .

Evenzo, wanneer de input-changed aangepaste gebeurtenis wordt geactiveerd op <some-input> , de myInput eigenschap op de hostcomponent wordt ingesteld op de gebeurtenis detail.value eigendom.

In onze <polymer-lazy-image> sjabloon, gebruiken we geen binding in twee richtingen, dus houden we het bij vierkante haken.

De aria-hidden attribuut vormt een kleine uitdaging. Polymeer bindt Booleaanse waarden aan attribuut met setAttribute(name, '') en removeAttribute(name) . Maar sinds aria-hidden moet de letterlijke tekenreeks "true" nemen of "false" , we kunnen het niet zomaar binden aan de Booleaanse waarde van intersecting . De <img/> src is even interessant. Echt, we willen het pas instellen nadat het element elkaar heeft gekruist. Daarvoor moeten we de eigenschap src op de afbeelding berekenen op basis van de status van de intersecting eigendom.

Polymeersjablonen kunnen berekende bindingen . bevatten . Deze zijn gebonden aan de retourwaarde van de gekozen methode.

<img id="image"
    aria-hidden$="[[computeImageAriaHidden(intersecting)]]"
    src="[[computeSrc(intersecting, src)]]"
    alt$="[[alt]]"
/>

Wat is er met deze functie-achtige syntaxis in onze bindende expressies? Dat vertelt Polymer welke elementmethode moet worden uitgevoerd en wanneer. Het wordt geactiveerd elke keer dat de afhankelijkheden (d.w.z. de 'argumenten doorgegeven' in de bindingsuitdrukking) veranderen, waarbij de binding wordt bijgewerkt met de geretourneerde waarde.

Houd er ook rekening mee dat we gebonden zijn aan de src eigendom op de afbeelding, niet het is attribuut . Dat is om te voorkomen dat u probeert een afbeelding te laden op URL "undefined" .

computeSrc(intersecting, src) {
  // when `intersecting` or `src` change,
  return intersecting ? src : undefined;
}

computeImageAriaHidden(intersecting) {
  // when `intersecting` changes,
  return String(!intersecting);
}

Laat u echter niet misleiden, dit zijn geen JavaScript-expressies, dus u kunt geen waarde doorgeven die u wilt:[[computeImageAriaHidden(!intersecting)]] werkt niet, en [[computeImageAriaHidden(this.getAttribute('aria-hidden'))]] ook niet

Nu passen we onze eigendomskaart en stijlen iets aan om rekening te houden met de wijzigingen in de API van ons element:

static get properties() {
  return {
    // ...

    /** Whether the element is intersecting. */
    intersecting: Boolean,

    /**
     * Whether the image has loaded.
     * @type {Boolean}
     */
    loaded: {
      type: Boolean,
      reflectToAttribute: true,
      value: false,
    },

  };
}
<style>
  /* ... */
  #placeholder ::slotted(*),
  :host([loaded]) #image {
    opacity: 1;
  }

  #image,
  :host([loaded]) #placeholder ::slotted(*) {
    opacity: 0;
  }
</style>

<div id="placeholder" aria-hidden$="[[computePlaceholderAriaHidden(intersecting)]]">
  <slot name="placeholder"></slot>
</div>

<img id="image"
    aria-hidden$="[[computeImageAriaHidden(intersecting)]]"
    src="[[computeSrc(intersecting, src)]]"
    alt$="[[alt]]"
    on-load="onLoad"
/>

We waren dus in staat om de boilerplate aanzienlijk te verminderen in onze component, en verklein een deel van de overtollige logica door deze in onze sjabloon op te nemen, zij het met een paar enigszins vermoeiende computerbindhulpmiddelen.

Hier is onze ingevulde <polymer-lazy-image> module:

import { PolymerElement, html } from '@polymer/polymer';

const isIntersecting = ({isIntersecting}) => isIntersecting;

const tagName = 'polymer-lazy-image';

class PolymerLazyImage extends PolymerElement {
  static get template() {
    return html`
      <style>
        :host {
          position: relative;
        }

        #image,
        #placeholder ::slotted(*) {
          position: absolute;
          top: 0;
          left: 0;
          transition:
            opacity
            var(--lazy-image-fade-duration, 0.3s)
            var(--lazy-image-fade-easing, ease);
          object-fit: var(--lazy-image-fit, contain);
          width: var(--lazy-image-width, 100%);
          height: var(--lazy-image-height, 100%);
        }

        #placeholder ::slotted(*),
        :host([loaded]) #image {
          opacity: 1;
        }

        #image,
        :host([loaded]) #placeholder ::slotted(*) {
          opacity: 0;
        }
      </style>

      <div id="placeholder" aria-hidden$="[[computePlaceholderAriaHidden(intersecting)]]">
        <slot name="placeholder"></slot>
      </div>

      <img id="image"
        aria-hidden$="[[computeImageAriaHidden(intersecting)]]"
        src="[[computeSrc(intersecting, src)]]"
        alt$="[[alt]]"
        on-load="onLoad"
      />
    `;
  }

  static get properties() {
    return {
      /** Image alt-text. */
      alt: String,

      /** Whether the element is on screen. */
      intersecting: Boolean,

      /** Image URI. */
      src: String,

      /**
       * Whether the image has loaded.
       * @type {Boolean}
       */
      loaded: {
        type: Boolean,
        reflectToAttribute: true,
        value: false,
      },

    };
  }

  constructor() {
    super();
    this.observerCallback = this.observerCallback.bind(this);
  }

  connectedCallback() {
    super.connectedCallback();
    // Remove the wrapping `<lazy-image>` element from the a11y tree.
    this.setAttribute('role', 'presentation');
    // if IntersectionObserver is available, initialize it.
    if ('IntersectionObserver' in window) this.initIntersectionObserver();
    // if IntersectionObserver is unavailable, simply load the image.
    else this.intersecting = true;
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    this.disconnectObserver();
  }

  /**
   * Loads the img when IntersectionObserver fires.
   * @param  {Boolean} intersecting
   * @param  {String} src
   * @return {String}
   */
  computeSrc(intersecting, src) {
    return intersecting ? src : undefined;
  }

  /**
   * "true" when intersecting, "false" otherwise.
   * @protected
   */
  computePlaceholderAriaHidden(intersecting) {    
    return String(intersecting);
  }

  /**
   * "false" when intersecting, "true" otherwise.
   * @protected
   */
  computeImageAriaHidden(intersecting) {
    return String(!intersecting);
  }

  /** @protected */
  onLoad() {
    this.loaded = true;
  }

  /**
   * Sets the `intersecting` property when the element is on screen.
   * @param  {[IntersectionObserverEntry]} entries
   * @protected
   */
  observerCallback(entries) {
    if (entries.some(isIntersecting)) this.intersecting = true;
  }

  /**
   * Initializes the IntersectionObserver when the element instantiates.
   * @protected
   */
  initIntersectionObserver() {
    if (this.observer) return;
    // Start loading the image 10px before it appears on screen
    const rootMargin = '10px';
    this.observer = new IntersectionObserver(this.observerCallback, { rootMargin });
    this.observer.observe(this);
  }

  /**
   * Disconnects and unloads the IntersectionObserver.
   * @protected
   */
  disconnectObserver() {
    this.observer.disconnect();
    this.observer = null;
    delete this.observer;
  }
}

customElements.define(tagName, PolymerLazyImage);

Bekijk het verschil tussen de vanille- en polymeerversies en zie het onderdeel aan het werk:

Meer polymeerfuncties

Polymer heeft meer te bieden dan ons eenvoudige voorbeeldelement gemakkelijk kan aantonen. Een klein voorbeeld is de manier waarop Polymer alle id . in kaart brengt 'd elementen in uw sjabloon naar een object met de naam $ :

<paper-button id="button">Button!</paper-button>
<paper-input id="input" label="Input!"></paper-input>
connectedCallback() {
  console.log(this.$.button.textContent) // "Button!"
  this.$.input.addEventListener('value-changed', breakTheInternet);
}

Geavanceerde gegevensbinding

Polymeer kan ook binden aan gastheereigenschappen van gebeurtenissen van niet-polymeerelementen met een speciale syntaxis:

<video current-time="{{videoTime::timeupdate}}"/>

Dit betekent "wanneer de timeupdate gebeurtenis vuurt, wijs de lokale videoTime . toe eigenschap toe aan de currentTime . van het video-element ".

In een latere versie van <polymer-lazy-image> , kunnen we dit soort bindingen gebruiken om interne <img> te synchroniseren eigendommen met die van ons.

Lees de documenten eens door voor meer informatie over het gegevensbindingssysteem van Polymer.

Waarnemers en berekende eigenschappen

Berekende eigenschappen en bindingen zijn gespecialiseerde gevallen van Polymer waarnemers . Een eenvoudige waarnemer ziet er als volgt uit:

static get properties() {
  return {
    observed: {
      type: String,
      observer: 'observedChanged',
    },
  };
}

observedChanged(observed, oldVal) {
  console.log(`${ observed } was ${ oldVal }`);
}

Je kunt ook complexe waarnemers definiëren die meerdere afhankelijkheden nemen of objecten of arrays diep observeren.

static get properties() {
  return {
    observed: Object,
    message: {
      type: String,
      value: 'A property of observed has changed',
    },
  };
}

static get observers() {
  return [
    // careful: deep observers are performance intensive!
    'observedChanged(message, observed.*)'
  ],
}

observedChanged(message, { path, value, base }) {
  // path: the path through the object where the change occurred
  // value: the new value at that path
  // base: the root object e.g. `observed`
  console.log(message, path + ': ' + value);
}

U kunt ook berekende eigenschappen instellen, vergelijkbaar met berekende bindingen:

static get properties() {
  return {
    theString: String,
    theLength: {
      type: Number,
      computed: 'computeTheLength(theString)',
    },
  };
}

computeTheLength(theString) {
  return theString.length;
}

In dat geval theLength wordt bijgewerkt volgens computeTheLength wanneer theString wijzigingen.

Deze berekende eigenschappen kunnen dan zoals elke normale eigenschap aan uw sjabloon worden gekoppeld.

<span>[[theString]] has [[theLength]] characters</span>

Lees alles over Polymer-waarnemers in de documenten.

Eigenschappenbeschrijvingen

We hebben al gezien hoe we reflectToAttribute . kunnen instellen en notify om de buitenwereld te beïnvloeden wanneer onze waarden worden bijgewerkt, en hoe u eenvoudige waarnemers kunt instellen met de observer beschrijving.

Je kunt ook een standaardwaarde instellen met value , die een letterlijke waarde of een functie aanneemt.

static get properties() {
  return {
    prop: {
      type: String,
      value: '🚣‍♂️'
    },

    things: {
      type: Array,
      value: () => [],
    },
  };
}

Wees voorzichtig! Als u een standaardwaarde wilt instellen met een referentietype zoals Array of Object , zorg ervoor dat u een functie doorgeeft, of anders elke instantie van uw element zal dezelfde referentie delen.

value toewijzingen worden eenmaal ingesteld wanneer het onderdeel wordt geïnitialiseerd en worden daarna niet opnieuw bijgewerkt. Als je eigenschappen dynamisch moet instellen nadat je verbinding hebt gemaakt, gebruik dan berekende eigenschappen of waarnemers.

Helper-elementen

Polymer wordt geleverd met een paar hulpelementen die u in uw sjablonen kunt gebruiken om de hoeveelheid dwingende JavaScript die u moet schrijven te verminderen. De twee meest gebruikte zijn <dom-repeat> voor het doorlopen van lijsten en het uitvoeren van DOM, en <dom-if> voor voorwaardelijke weergave:

<!-- Will output a new article with h2 and img for each post -->
<dom-repeat items="[[posts]]" as="post">
  <template>
    <article>
      <h2>[[post.title]]</h2>
      <img src$="[[post.picture]]">
    </article>
  </template>
</dom-repeat>

<!-- Will only render it's template if conditionDepending(someProp, another) is truthy -->
<dom-if if="[[conditionDepending(someProp, another)]]">
  <template>
    I'm a very lucky textNode to have [[someProp]] and [[another]] on my side.
  </template>
</dom-if>

Als u deze helpers wilt gebruiken, moet u ze importeren

import '@polymer/polymer/lib/elements/dom-repeat.js';
import '@polymer/polymer/lib/elements/dom-if.js';

Zie de Polymer-documenten voor meer informatie over de hulpelementen.

Polymeer-apps samenstellen

Polymer schittert echt als het gaat om het factoriseren van hele apps. Het Polymer Project was de pionier van een behoorlijk vooruitstrevende en duidelijk speciale (sorry) soort declaratieve app-structuur die grotendeels op HTML-elementen was gebouwd. De Polymer-benadering maakt van "alles een element", waarbij gebruik wordt gemaakt van de ingebouwde configureerbaarheid van HTML. Zo is er bijvoorbeeld de <iron-ajax> element, dat bronnen kan ophalen en blootstellen aan Polymer's databinding.

<iron-ajax auto
    url="/api/posts"
    handle-as="json"
    last-response="{{posts}}"></iron-ajax>

<dom-repeat items="[[posts]]" as="post">
  <template>
    <article>
      <h2>[[post.title]]</h2>
      <img hidden$="[[!post.cover]]" src$="[[post.cover]]">
      [[post.body]]
    </article>
  </template>
</dom-repeat>

Maar naar mijn bescheiden mening is het beste voorbeeld van deze aanpak de <app-route> element en het idee van ingekapselde routering:

<!-- <app-shell> template -->

<!-- Capture and expose address-bar changes -->
<app-location route="{{route}}"></app-location>

<app-route route="[[route]]"
    data="{{routeData}}"
    tail="{{pageTail}}"
    pattern="/:page"></app-route>

<!-- Composed routing! -->
<app-route route="[[tail]]"
    data="{{itemData}}"
    tail="{{itemTail}}"
    pattern="/:itemId"></app-route>

<iron-pages selected="{{routeData.page}}" attr-for-selected="name">
  <app-master name="master"></app-master>
  <app-detail name="detail"
      item-id="[[itemData.itemId]]"
      route="[[itemTail]]"></app-detail>
</iron-pages>

Met behulp van app-route en iron-pages-elementen hebben we een complete routeringsoplossing die inhoud op basis van de URL verbergt en toont, en zelfs routegerelateerde gegevens doorgeeft aan die weergavecomponenten.

En sinds <app-route> duurt het is route eigenschap als data, niet direct gekoppeld aan window.location , kunt u delen van de route doorgeven aan onderliggende weergaven en ze hun eigen interne status laten beheren met hun eigen <app-route> kinderen. Netjes!

<!-- <app-detail> template -->
<app-route route="[[route]]"
    data="{{routeData}}"
    pattern="/:editing"></app-route>

<item-detail hidden$="[[routeData.editing]]"></item-detail>
<item-editor hidden$="[[!routeData.editing]]"></item-editor>

<paper-checkbox checked="{{routeData.editing}}">Editing</paper-checkbox>

Wat een gaaf concept!

**Merk** op dat we omwille van de beknoptheid in dit voorbeeld rechtstreeks binden aan subeigenschappen van `routeData`, maar in een echt project zouden we enkele hulpmethoden toevoegen om een ​​tussenliggende `page`-eigenschap van `routeData' te berekenen `.

Zie de eerbiedwaardige Polymer Starter Kit op GitHub voor een volledig gerealiseerd voorbeeld van dit type app-architectuur.

Polymeer / polymeer-starterkit

Een startpunt voor Polymer-apps

Polymeer App Toolbox - Starter Kit

Deze sjabloon is een startpunt voor het bouwen van apps met een op laden gebaseerde lay-out. De lay-out wordt verzorgd door app-layout elementen.

Deze sjabloon, samen met de polymer-cli toolchain, demonstreert ook het gebruik van het "PRPL-patroon". Dit patroon maakt een snelle eerste levering en interactie met de inhoud mogelijk op de oorspronkelijke route die door de gebruiker is gevraagd, samen met een snelle daaropvolgende navigatie door de resterende componenten die nodig zijn voor de app vooraf in de cache te plaatsen en ze geleidelijk on-demand te laden terwijl de gebruiker door de app navigeert.

Het PRPL-patroon in een notendop:

  • Duwen componenten die nodig zijn voor de initiële route
  • Renderen initiële route zo snel mogelijk
  • Pre-cache componenten voor resterende routes
  • Lazy-load en upgrade geleidelijk volgende routes op aanvraag

Instellen

Vereisten

Installeer Polymer CLI met npm (we nemen aan dat u node.js vooraf hebt geïnstalleerd).

npm install -g polymer-cli
Project initialiseren vanuit sjabloon
mkdir my-app
cd my-app
polymer init polymer-3-starter-kit

Start de ontwikkelserver

Dit commando dient...

Weergeven op GitHub

Papieren elementen

Het zou geen blogpost over Polymer zijn als we de Paper Elements niet zouden noemen, de set UI-componenten voor materiaalontwerp die zijn gepubliceerd door het Polymer Project. Maar we zouden ook een grote fout maken als we één ding niet superduidelijk zouden krijgen:

PaperElements != Polymer;

Je kunt de polymeerbibliotheek prima gebruiken zonder de papierelementen, en je kunt de papierelementen prima gebruiken zonder de polymeerbibliotheek!

<head>
  <script type="module" src="https://unpkg.com/@polymer/paper-checkbox/paper-checkbox.js?module"></script>
  <script type="module" src="https://unpkg.com/@polymer/paper-card/paper-card.js?module"></script>
  <script type="module" src="https://unpkg.com/@polymer/paper-button/paper-button.js?module"></script>
</head>  
<body>
  <paper-card heading="Am I Checked?">
    <div class="card-content">
      Output: <span id="output">Not Checked</span>
    </div>
    <div class="card-actions">
      <paper-checkbox id="input">Check me!</paper-checkbox>
      <paper-button raised disabled id="button">Reset</paper-button>
    </div>
  </paper-card>
  <script>
    const onClick = () => input.checked = false;
    const onInput = ({detail: { value }}) => {
      output.textContent = value ? 'Checked' : 'Not Checked';
      button.disabled = !value;
    }

    input.addEventListener('checked-changed', onInput);
    button.addEventListener('click', onClick);
  </script>
</body>

Het enige dat we hier verliezen is de mogelijkheid om Polymer's databindingssysteem te gebruiken. Maar - je raadt het al - daar is een element voor, genaamd <dom-bind>

Als u op zoek bent naar een op materiaalontwerp gebaseerde gebruikersinterface zonder poespas, probeer dan de papieren elementen eens.

Polymeergereedschap

Het Polymer Project publiceert - naast hun belangenbehartiging, JS- en componentbibliotheken en standaardvoorstellen - ook een verscheidenheid aan tools waarmee u uw apps en componenten kunt bouwen, publiceren en aanbieden.

prpl-server

Het Chrome-team ontwikkelde het PRPL-patroon als best practice voor het schrijven en leveren van performante web-apps. prpl-server maakt het gemakkelijk om de kleinste effectieve bundel aan geschikte browsers aan te bieden, terwijl oudere browsers met grotere bundels nog steeds worden ondersteund. Er is een kant-en-klaar binair bestand en een express middleware-bibliotheek. Probeer het eens.

Polymeer CLI

De Vue CLI helpt je bij het ontwikkelen van Vue-apps. De Angular CLI helpt je bij het ontwikkelen van Angular-apps. create-react-app helpt je bij het ontwikkelen van React-apps.

De Polymer CLI helpt u bij het ontwikkelen van web apps.

Toegegeven, het biedt sjablonen voor Polymer 3-elementen en apps, maar dat is niet alles. De polymer build en polymer serve commando's zullen alle webcomponent-apps bouwen en bedienen. Transpilatie is optioneel. In feite is vrijwel het enige dat de CLI met uw code doet, het vervangen van kale modulespecificaties zoals import { PolymerElement } from '@polymer/polymer'; naar relatieve URL's die de browser direct kan laden.

Ja. Dat is precies waar ik het over heb. De volgende keer dat u een app-project heeft, kunt u overwegen dit te combineren met webcomponenten en de Polymer CLI.

Maar als je wilt om te transpileren voor oudere browsers (zie prpl-server hierboven), kunt u een builds . definiëren sectie van polymer.json :

{
  "root": "~/projects/my-project",
  "entrypoint": "index.html",
  "shell": "src/my-project.js",
  "sources": [
   "src/my-project.js",
   "manifest/**",
   "package.json"
  ],
  "builds": [{
      "name": "es5prod",
      "preset": "es5-bundled",
      "addServiceWorker": true
    }, {
      "name": "es6prod",
      "preset": "es6-unbundled",
      "addServiceWorker": true
    }, {
      "name": "dev",
      "addServiceWorker": false,
      "js": {"minify": false, "compile": false},
      "css": {"minify": false},
      "html": {"minify": false},
      "bundle": false,
      "addPushManifest": false
    }]
}

Dan configureer je gewoon prpl-server om es6prod te dienen naar moderne browsers en es5prod naar IE en vrienden, en je gaat naar de races.

Lees ze documenten, doc!

WebComponents.org

Voordat je er vandoor gaat om die <super-button> te implementeren je in gedachten hebt, waarom zou je niet eens zoeken op webcomponents.org, de grootste directory met webcomponenten.
Elk element wordt getoond met zijn documentatie, openbare API en installatiemethode. Je vindt er ook links naar npm en github.
Als u een componentauteur bent, aarzel dan niet! Publiceer uw componenten zodat anderen ervan kunnen profiteren.

Conclusies

De Polymer-bibliotheek was zijn tijd onmiskenbaar vooruit. Het nam de aanpak om beter van het webplatform te eisen en dat vervolgens te realiseren, in plaats van alleen maar om de beperkingen van het platform heen te werken.

Heeft de Polymer-bibliotheek, nu webcomponenten breed worden ondersteund, nog een plaats in onze web-dev-toolbox? Zeker wel! Sommige projecten lenen zich natuurlijk voor de declaratieve stijl van Polymer. Sommige teams zullen ontdekken hoe ontwerpers en documentauteurs het werk van ontwikkelaars kunnen doen met het expressieve bindsysteem van Polymer.

Het is echter niet alles ☀️ en 🌹🌹. Naarmate het platform en de bredere webgemeenschap zich hebben ontwikkeld, hebben ook de prioriteiten van het Polymer-project zich ontwikkeld. Polymer 3 zal waarschijnlijk de laatste grote release van de bibliotheek zijn, en evenzo zal de 3.0-serie de laatste release van de papieren elementen zijn.

Laten we dus enkele van de voor- en nadelen van de Polymer-bibliotheek bekijken:

Pros Nadelen
Expressief sjabloonsysteem Kan JS niet rechtstreeks doorgeven aan sjablonen
Waarnemers en berekende eigenschappen, declaratieve gebeurtenisluisteraars Grote afhankelijkheidsketen stimuleert grotere apps met alleen polymeren
Super coole en unieke benadering van declaratieve app-structuur Voor beter of slechter, deze unieke declaratieve stijl is niet zo populair als andere architecturen
Een volwassen bibliotheek en componentenset. Geprobeerd, getest en waar Polymer.js is vrijwel verouderd en zal geen nieuwe functies ontvangen, tenzij gevorkt

Betekent dat dan het einde voor Web Components? Heck nee! Polymer is verre van het enige spel in de stad. Een lichtgewicht, declaratieve JS-templatingbibliotheek genaamd lit-html en een basisklasse met aangepaste elementen die deze gebruikt, genaamd LitElement zijn de nieuwe hotness. Als God het wil, behandelen we ze in onze volgende aflevering.

Tot dan 😊

Wil je een een-op-een mentoring sessie over een van de hier behandelde onderwerpen?

Dankbetuigingen

Dank in willekeurige volgorde aan Pascal Schilp en @ruphin voor hun suggesties en correcties.

Bekijk het volgende artikel in de serie