$30 off During Our Annual Pro Sale. View Details »

Custom Elements Everywhere

Rob Dodson
August 25, 2017

Custom Elements Everywhere

Video: https://www.youtube.com/watch?v=sK1ODp0nDbM

If you're a mid to large size company, building your UI in Web Components and sharing it across teams on different stacks sounds like a great plan. In theory things should "just work," but reality often proves otherwise. What do we need to do to make sure our components work well with all of the major frameworks and conversely what do the frameworks need to do to work with our components? This talk explores both sides of the problem and lays out some best practices that both parties can use to ensure seamless interoperability.

Rob Dodson

August 25, 2017
Tweet

More Decks by Rob Dodson

Other Decks in Technology

Transcript

  1. View Slide

  2. Polymer Summit 2017
    Rob Dodson
    @rob_dodson
    Custom Elements
    Everywhere

    View Slide

  3. I've been at Google
    for 3+ years

    View Slide

  4. View Slide

  5. View Slide

  6. Custom Elements should
    work everywhere.

    View Slide

  7. Custom Elements should
    work everywhere. So…
    why don't they?

    View Slide

  8. Did we put the
    before the
    unihorse

    View Slide

  9. TWO PARTS
    PART no. 1
    What is a "good"
    Custom Element?
    PART no. 2
    How should
    frameworks act?

    View Slide

  10. What is a "good"
    Custom Element?

    View Slide

  11. Do you know if there are any
    reference Custom Elements I
    could look at which live up to
    this standard?
    Not that I'm aware of.

    View Slide

  12. View Slide

  13. SCIENCE!

    View Slide

  14. HowTo: Components
    A collection of educational
    custom elements that
    demonstrate best practices.
    Examples are meant to be read,
    interpreted, and ported to your
    own elements.

    View Slide

  15. super();
    this.attachShadow({mode: 'open'});
    this.shadowRoot.appendChild(template.conte
    }
    connectedCallback() {
    if (!this.hasAttribute('role'))
    this.setAttribute('role', 'checkbox');
    if (!this.hasAttribute('tabindex'))
    this.setAttribute('tabindex', 0);
    this._upgradeProperty('checked');
    this._upgradeProperty('disabled');
    this.addEventListener('keydown', this._onK
    The construtor is a good place to
    create Shadow DOM, though you
    should avoid touching any
    attributes or Light DOM children
    as they may not be available yet.
    connectedCallback fires when
    the element is inserted into the
    DOM. It's a good place to set the
    initial role, tabindex, internal
    state, and install event listeners.
    A user may set a property on an
    instance of an element,
    before its prototype has been
    connected to this class.
    The `_upgradeProperty` method

    View Slide

  16. HowTo:
    Components
    are a work in
    progress.

    View Slide

  17. Check it out @
    bit.ly/howto-components

    View Slide

  18. Shadow
    DOM
    Attributes &
    Properties
    Events

    View Slide

  19. Avoid absolutism

    View Slide

  20. Shadow DOM

    View Slide

  21. Does your element need
    Shadow DOM?

    View Slide

  22. You gotta use
    Shadow DOM! You gotta use
    Shadow DOM!

    View Slide

  23. View Slide


  24. howto-checkbox.js + howto-checkbox.css

    View Slide

  25. If someone puts your element
    inside another shadow root,
    you'll have to figure out how
    to get your styles in there.

    View Slide

  26. w3c/webcomponents #468

    View Slide

  27. Create a shadow root if you
    want to self-apply styles.

    View Slide

  28. We have CSS-in-JS for
    scoping styles, so why do
    we need Shadow DOM?

    View Slide

  29. const template = document.createElement('template');
    template.innerHTML = `Count: `;
    Polymer Summit 2017

    View Slide

  30. export class CountWithShadow extends HTMLElement {
    constructor () {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.appendChild(template.content.cloneNode(true));
    }
    }
    customElements.define('x-count-with-shadow', CountWithShadow);
    Polymer Summit 2017

    View Slide

  31. export class CountWithoutShadow extends HTMLElement {
    constructor () {
    super();
    }
    connectedCallback() {
    this.appendChild(template.content.cloneNode(true));
    }
    }
    customElements.define('x-count-without-shadow', Count);
    Polymer Summit 2017

    View Slide

  32. // Meanwhile, in a React component…
    render () {
    const { count } = this.state; // increments every second
    return (

    {count}
    {count}

    );
    }
    Polymer Summit 2017

    View Slide

  33. Count: 1
    1
    Count:

    View Slide

  34. Count: 1
    1
    Count:

    1

    View Slide

  35. Count: 1
    1
    Count:

    1
    Count:

    View Slide

  36. Count: 1
    1
    Count:

    1
    Count:

    ummm…

    View Slide


  37. 2

    Count: 2
    2

    View Slide

  38. Place any children you create
    into a shadow root.

    View Slide

  39. These children are your
    implementation and
    should be hidden.

    View Slide

  40. Does your element need
    Shadow DOM?

    View Slide

  41. Yeah, it probably does.

    View Slide

  42. Without Shadow DOM you
    lose all guarantees of safety.

    View Slide

  43. Create a shadow root if you want to
    self-apply styles.
    Place any children you create into a
    shadow root.
    Shadow
    DOM

    View Slide

  44. Attributes &
    Properties

    View Slide

  45. Hey!
    HTML has a spec!

    View Slide

  46. But HTML is also
    kinda gnarly

    View Slide

  47. Accept primitive data as
    either attributes or properties;
    ideally, both.

    View Slide

  48. Reflect primitive data from
    attribute to property, and
    vice versa.

    View Slide

  49. View Slide

  50. class CustomVideo extends HTMLElement {
    get preload() {
    const value = this.getAttribute('preload');
    return value === null ? 'auto' : value;
    }
    set preload(value) {
    this.setAttribute('preload', value);
    }
    }
    Polymer Summit 2017

    View Slide

  51. class CustomVideo extends HTMLElement {
    get preload() {
    const value = this.getAttribute('preload');
    return value === null ? 'auto' : value;
    }
    set preload(value) {
    this.setAttribute('preload', value);
    }
    }
    Polymer Summit 2017
    default value

    View Slide

  52. class CustomVideo extends HTMLElement {
    get preload() {
    const value = this.getAttribute('preload');
    return value === null ? 'auto' : value;
    }
    set preload(value) {
    this.setAttribute('preload', value);
    }
    }
    Polymer Summit 2017

    View Slide

  53. View Slide

  54. Only accept rich data
    as properties.

    View Slide

  55. Do NOT reflect rich data
    properties to attributes.

    View Slide

  56. It's expensive to reflect.

    View Slide

  57. A serialized object will lose
    its identity.

    View Slide

  58. Accept primitive data as either
    attributes or properties.
    Reflect primitive data from
    attribute to property, and vice versa.
    Only accept rich data as properties.
    Do NOT reflect rich data from
    property to attribute.
    Attributes &
    Properties

    View Slide

  59. Events

    View Slide

  60. It's superfluous and may
    cause infinite loops.

    View Slide

  61. Do NOT dispatch events in
    response to the host setting a
    property (downward data
    flow).

    View Slide

  62. Do dispatch events in
    response to internal
    element activity.

    View Slide

  63. The element knows
    something changed, but
    the host does not.

    View Slide

  64. Do NOT dispatch events in response
    to the host setting a property
    (downward data flow).
    Do dispatch events in response to
    internal element activity.
    Events

    View Slide

  65. What is a "good"
    Custom Element?

    View Slide

  66. Resize This Window
    Building Components.
    A guide to Custom Element best practices.

    View Slide

  67. Check it out @
    bit.ly/building-components

    View Slide

  68. How should
    frameworks act?

    View Slide

  69. Billions and billions…

    View Slide

  70. Custom Elements Everywhere
    Making sure frameworks and
    custom elements can be BFFs

    View Slide

  71. custom-elements-everywhere.com

    View Slide

  72. View Slide

  73. Rob waves his arms
    around and apologizes
    [ ]

    View Slide

  74. known unknowns

    View Slide

  75. Custom Element test scores

    View Slide

  76. Attributes &
    Properties
    Events

    View Slide

  77. Attributes &
    Properties

    View Slide

  78. Manual
    The developer tells the
    framework how to pass data.
    Automated
    The framework determines
    how to pass data.

    View Slide

  79. Angular

    View Slide

  80. Angular

    set the foo property equal to bar

    View Slide

  81. Angular

    i.e. myElement.foo = bar

    View Slide

  82. Angular


    set the baz attribute equal to squid

    View Slide

  83. Angular


    i.e. myElement.setAttribute('baz', squid)

    View Slide

  84. Vue

    set the foo attribute equal to bar

    View Slide

  85. Vue

    set the foo property equal to bar

    View Slide

  86. React

    set the foo attribute equal to bar

    View Slide

  87. React


    View Slide

  88. React
    You can work around this by
    imperatively grabbing a reference
    to the element and setting the
    property on it.

    View Slide

  89. React
    Resize This Window
    facebook/react #10399

    View Slide

  90. Preact

    View Slide

  91. Preact

    set the foo property if it's available

    View Slide

  92. Preact
    if ('foo' in myElement)
    myElement.foo = bar
    else
    myElement.setAttribute('foo', bar)

    View Slide

  93. Preact
    if ('foo' in myElement)
    myElement.foo = bar
    else
    myElement.setAttribute('foo', bar)

    View Slide

  94. Preact
    if ('foo' in myElement)
    myElement.foo = bar
    else
    myElement.setAttribute('foo', bar)

    View Slide

  95. Preact
    if ('foo' in myElement)
    myElement.foo = bar
    else
    myElement.setAttribute('foo', bar)

    View Slide

  96. Events

    View Slide

  97. Angular

    View Slide

  98. Angular

    myElement.addEventListener('foochanged', onFoo)

    View Slide

  99. Angular

    lowercase

    View Slide

  100. Angular

    CAPSCase

    View Slide

  101. Angular

    camelCase

    View Slide

  102. Angular

    kebab-case

    View Slide

  103. Angular

    PascalCase

    View Slide

  104. Angular

    AsShOlEcAsE

    View Slide

  105. View Slide

  106. Vue

    View Slide

  107. Vue

    same behavior as Angular

    View Slide

  108. React

    View Slide

  109. React

    unfortunately won't work.

    View Slide

  110. React
    You can work around this by
    imperatively grabbing a reference
    to the element and adding an
    event listener.

    View Slide

  111. React
    Resize This Window
    facebook/react #7901

    View Slide

  112. Preact

    View Slide

  113. Preact

    TOTES WORKS!

    View Slide

  114. Preact

    TOTES WORKS!
    kinda. sorta. mostly…

    View Slide

  115. Preact

    always calls .toLowerCase()
    myElement.addEventListener('foochanged', onFoo)

    View Slide

  116. So…

    View Slide

  117. So…
    it doesn't support
    AsShOlEcAsE?!?!

    View Slide

  118. Preact
    Resize This Window
    developit/preact #788

    View Slide

  119. So, what did we learn?

    View Slide

  120. There are best practices
    we can all follow

    View Slide

  121. What's broken today
    seems easy to fix

    View Slide

  122. amazing
    people
    Reviewers
    HowTo: Components team
    Sean Rob Jason Evan
    Dan Domenic Steve Dominic
    Surma Monica Ewa

    View Slide

  123. Thank You!
    Polymer Summit 2017
    Rob Dodson
    @rob_dodson
    Images by Simon Child, Eucalyp, Edward Boatman from the Noun Project.

    View Slide