Buslowicz / twc

TypeScript based, boilerplate-less, Polymer toolbox friendly Polymer Modules
32 stars 1 forks source link

Support for lazy-imports #108

Open tpluscode opened 7 years ago

tpluscode commented 7 years ago

I've just realized that with TWC it is not possible to have additional markup inside of the <dom-module> which is how <lazy-imports> are used:

<dom-module id="my-element">
  <link rel="lazy-import" group="main" href="../loaded-lazily.html">

  <template>
  </template>

  <script>
    class MyElement extends Polymer.Element {}
  </script>

</dom-module>

Or is it possible?

Buslowicz commented 7 years ago

Hmm, nope not possible, never played with lazy loading :(. Any idea how to mark import as lazy in TypeScript? I mean what syntax should we use?

tpluscode commented 7 years ago

I see where you're going but I wouldn't necessarily bake this into twc concretely. After all lazy-imports only one possible way to achieve... lazy imports. It's just a nuisance that it requires putting additional markup inside <dom-module> as a sibling of <template>.

Instead of focusing on imports maybe it wouldn't be too cumbersome to put any extra HTML outside the element's template?

tpluscode commented 7 years ago

The best bad idea I have is an additional annotation but it would just increase fragmentation.

Buslowicz commented 7 years ago

How about this:

import { CustomElement } from 'twc/polymer';

@CustomElement()
class MyElement extends Polymer.Element {
  // importing and returning the modules
  async importGroupA() {
    return {
      ...await import('bower:paper-button/paper-button.html'),
      ...await import('bower:paper-behaviors/paper-inky-focus-behavior.html')
    };
  }
  // just importing the modules
  async importGroupB() {
    await import('bower:paper-behaviors/paper-button-behavior.html');
    await import('bower:paper-behaviors/paper-checked-element-behavior.html');
  }

  connectedCallback() {
    // do the actual import of group A
    this.importGroupA()
      // then do something with imported assets (or just after it imports)
      .then(({ PaperButton, PaperInkyFocusBehavior }) => {
        console.log('loaded components:', PaperButton, PaperInkyFocusBehavior);
      });
    // do the actual import of group B (does not wait for anything)
    this.importGroupB();
  }
}

This would translate to:

<dom-module id="upgrade-button">
  <link rel="lazy-import" group="import-group-a" href="../paper-button/paper-button.html">
  <link rel="lazy-import" group="import-group-a" href="../paper-behaviors/paper-inky-focus-behavior.html'">
  <link rel="lazy-import" group="import-group-b" href="../paper-behaviors/paper-button-behavior.html">
  <link rel="lazy-import" group="import-group-b" href="../paper-behaviors/paper-checked-element-behavior.html">
  <template>
    ...
  </template>
  <script>
    class MyElement extends Polymer.LazyImportsMixin(Polymer.Element) {
      static get is() { return 'my-element'; }
      connectedCallback() {
        this.importLazyGroup('import-group-a').then(({ PaperButton, PaperInkyFocusBehavior }) => {
          console.log('loaded components:', PaperButton, PaperInkyFocusBehavior);
        });
        this.importLazyGroup('import-group-b');
      }
    }
    customElements.define(MyElement.is, MyElement);
  </script>
</dom-module>

Does this look ok? The idea is to pick up all the async methods with awaited imports (or maybe just methods with imports?) and put those imports into the <dom-module>, and every call of that method would be replaced with this.importLazyGroup(*method-name-dash-cased*). It would be possible to match both dot notation (this.methodName) and square bracket notation (this['methodName']). The only thing that remains an open question, is how to deal with computed method name or variables. TWC does not evaluate the runtime, so for now dynamic group names would require to be called as this.importLazyGroup(dynamicName), which would probably lose the types in then callback.

tpluscode commented 7 years ago

Following our conversation, here's what we pretty much came up with:

  1. Add lazy imports to main Polymer interface?
interface LazyImportsMixin {  
   importLazyGroup<T>(this: T, groupName: keyof T): Promise<string[]>;
}

declare interface PolymerStatic {
   LazyImportsMixin: Mixin<LazyImportsMixin>;
}
  1. Use as in plain Polymer 2 element
import { CustomElement, lazyImportGroup } from 'twc/polymer';

@CustomElement()
class MyElement extends Polymer.LazyImportsMixin(Polymer.Element) {

  @lazyImportGroup()
  async groupA() {
    await import('bower:paper-input/paper-input.html');
  }

  connectedCallback() {
    this.importLazyGroup('groupA')
      .then((imported: string[]) => {
        console.log('loaded components:', imported);
      });
  }
}
  1. Which would generate
<dom-module id="my-element">
  <link rel="lazy-import" group="group-a" href="../paper-input/paper-input.html" />

  <script>
    class MyElement extends Polymer.LazyImportsMixin(Polymer.Element) {
      static get is() { return 'my-element'; }

      connectedCallback() {
        this.importLazyGroup('groupA')
          .then((imported) => {
            console.log('loaded components:', imported);
          });
      }
    }

    customElements.define(MyElement.is, MyElement);
  </script>
</dom-module>

There is a few things to note:

  1. I'm not sure an annotation is necessary on the groupA method
  2. twc could do additional check and warnings if key of some other member is provided. tsc will not complain about parameter such as connectedCallback, updateStyles, etc which are all members of MyElement
  3. the group name is kebab-cased like you proposed, right?
  4. there is no need to ever return anything from the groupA method
  5. for all that to work with typescript I needed this in tsconfig.json

    {
      "module": "esnext",
      "lib": [ "es2016" ]
    }