PolymerLabs / start-lit-element

Hello World app for LitElement.
68 stars 16 forks source link

Import element definition after `waitFor()` callback #3

Open zerodevx opened 5 years ago

zerodevx commented 5 years ago

WRT https://github.com/PolymerLabs/start-lit-element/blob/master/index.html#L16-L22,

<!-- Load polyfills --> 
<script 
  src="node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"
  defer>
</script> 

<!-- Change stuff here for your app -->
<script type="module" src="src/start-lit-element.js" defer></script>

it seems that if we're using webcomponents-loader.js asynchronously with the defer attribute, element definitions should be imported after the polyfills have been loaded via the WebComponents.waitFor() callback.

Per the webcomponents docs,

When using webcomponents-loader.js with the defer attribute, scripts that rely on the polyfills must be loaded using WebComponents.waitFor(loadCallback).

So perhaps the import procedure might be:

<!-- Load polyfills --> 
<script 
  src="node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"
  defer>
</script> 

<!-- Change stuff here for your app -->
<script type="module">
  WebComponents.waitFor(() => {
    // We can't use dynamic import() since it's not supported in FF and Edge
    // and webcomponents does not polyfill that.
    // For lack of a better idea:
    let el = document.createElement('script');
    el.src = 'src/start-lit-element.js';
    el.type = 'module';
    document.head.appendChild(el);
  });
</script>

I was wondering is this correct? Thanks.

ghost commented 5 years ago

Thanks for raising this, I'll look into it & update.

ghost commented 5 years ago

@zerodevx thanks again for raising this. I wanted to try to use dynamic import() as shown in the WebComponents README so I set up polymer.json to try to take care of the browser compatibility for that. I think it's correct now.

I had another issue when I tried this, though. Here's what I think was going on:

The browsers load these scripts in a different order. The example in the WebComponents README expects the deferred webcomponents-loader.js to complete before the <script type="module"> where it refers to WebComponents.waitFor. But Firefox calls the module script before running the loader, so when it's trying to set up the waitFor callback, it still doesn't know about window.WebComponents.

To work around that, I check for window.WebComponents and instead wait for WebComponentsReady if the browser hasn't run the loader yet. I think this does mean that Firefox waits longer than necessary to start loading the app shell, but it works, and it guarantees that custom elements won't start loading before the polyfills are ready.

Let me know if I missed something, it seemed to be working fine in the latest Chrome, Firefox, Safari, and Edge.

zerodevx commented 5 years ago

Apologies for the late reply. There is a somewhat related issue that should land before this I think.

It might not be a good idea to transpile native module to AMD - unless the server does some sort of UA detection to serve the transpiled payload only to super old browsers. The script load issue you mentioned is probably a side effect of transpilation. By spec, loading webcomponents-loader with defer - it should be placed near the top of <head> - will ensure that it's the first script to be eval upon readyState == 'interactive'; hence subsequent <script type="module"> declarations (which by default are deferred) will be sequentially run thereafter. So WebComponents.waitFor() is guaranteed to have been defined. The actual eval of each waitFor() should be performed on DOMContentLoaded.

Good news is dynamic import has finally landed on FF - only took them 2 years to fix that bug. 😱

That said, IMO web components generally don't require the import() syntax - in the sense that CE definitions don't function like a normal "module" should with export function foo(bar) {...} etc, so loading CE scripts the legacy way works fine as well...