2017-10-29

WebComponents via AMD - pragmatic view

What I need for apps is ability to use AMD dependencies for my and external web components:
  • use the external libs from their CDN on prod, and local on developer machine
  • own AMD-based JS loading web components via AMD (webpack, dojo, requirejs) dependencies
  • own HTML syntax web components which list dependencies in JS section via AMD
Why AMD API? It is running without any compilers/transpilers in browser and compatible with webpack in case of need the bundling.

Why AMD instead of ES6 imports?

While ES6 imports are giving ability to load module from relative path, that is not enough as libraries could be served from different locations including different domains. AMD provides runtime URL resolving based on configuration making a combination of local component development with CDN dependencies a breathe.

How to load WebComponents via AMD?

require([   "link-import!iron-demo-helpers/demo-pages-shared-styles.html|onload"
        ,   "link-import!iron-demo-helpers/demo-snippet.html"
        ,   "link-import!../test-element.html"
        ], (a,b,c)=> console.log(`${a}, ${b}, ${c} webComponents loaded`) );

Of course the AMD configuration should be set to define URL mappings and common script loaded

  var dojoConfig ={ async: true
                  , packages:
                      [ { name: "webcomponentsjs", location: webcomponent_root }
                      , { name: "polymer", location: polymer_root }
                      ]
                  , aliases:  [ [ "link-import"  , isLocal ? "/components/link-import/link-import.js" : "../../link-import.js" ]
                              , [ /(vaadin-)(.*)/, (m,x,y)=>`${vaadin_root}/${x}${y}`   ]
                              , [ /(iron-)(.*)/  , (m,x,y)=>`${iron_root}/${x}${y}`     ]
                              ]
                  , deps: [ "webcomponentsjs/webcomponents-lite" ] // common for all pages
                  };

Here the Polymer and Vaadin Elements are used as good example of WebComponent frameworks.

Your custom WebComponent could use Polymer 2.x syntax and defined as HTML. Just the script section will have a class declaration wrappd into define() and dependencies are listed as JS strings instead of link imports:

        const NAME = 'test-element'
        define( NAME
            ,   [   "link-import!polymer/polymer-element.html"
                ,   "link-import!iron-ajax/iron-ajax.html"
                ,   "link-import!vaadin-material-theme/vaadin-text-field.html" //,   "link-import!vaadin-valo-theme/vaadin-combo-box.html"
                ,   "link-import!vaadin-combo-box/vaadin-combo-box.html"
                ]
            , ()=>
            {
                class TestElement extends Polymer.Element
                {
                    static get is() { return 'test-element'; }

                    static get properties()
                    {
                        return  {   prop1: {
                                        type: String,
                                        value: 'test-element'
                                    }
                                };
                    }
                }

                window.customElements.define( TestElement.is, TestElement );
            });
        require([NAME]);

Where to find the working sample of loading and making WebComponent via AMD?

cdn.xml4jquery.com/ajax/libs/AmdHarness/link-import/test/demo/index.html

What library I need to use AMD loader in my WebComponent?

link-import on github or NPM

Want to go wild?

Help me with creation of module loader for JS and WebComponents as jQuery plugin. The async API for jQuery is already available via xml4jQuery, WebComponents integration is missing:
$('.my-el')
    .html('loading...')
    .linkImport('vaadin-combo-box/vaadin-combo-box.html') // path resolved by config
    .html('');
$('my-el')
    .html('loading...')
    .asWebComponent('vaadin-combo-box') // path resolved by config, WC loaded, and injected
    .prop('items',['red','green','blue]) // property assigned once WC is ready

//  will be auto-populated  even if the code run way later:
$.defineWebComponent( 'my-component' )
    .$on('load')
    .$then( ()=>$.get('colors.json') )
    .addWebComponent('vaadin-combo-box')
    .$then( (combo, colors)=> combo.items = colors );
Happy coding!