Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

On-demand custom elements using modules #444

Closed
andyearnshaw opened this issue Mar 16, 2016 · 5 comments
Closed

On-demand custom elements using modules #444

andyearnshaw opened this issue Mar 16, 2016 · 5 comments

Comments

@andyearnshaw
Copy link

One of our libraries is providing a growing library of custom elements, some of which are quite large and/or rely on other large third-party libraries. We don't really want to load in all these definitions because they aren't necessarily going to be used by developers using the library. To account for this, I wrote a minimal DeferredCustomElement interface that loads in the real definition when the createdCallback is called.

This works fine, though I'm not thrilled about mutating the class later and lifecycle callbacks have to be called from within the original definition. With these issues and some recent changes to the spec—particularly with observedAttributes, which would mean maintaining a list separate from the "real" element definition—, it's becoming less likely that this is going to hold up well in the long run.

One solution would be to require anyone using our custom elements to import the modules in their code. This has the following disadvantages:

  • Requires at least one script in the page to import the module if the behaviour is required, even if the API isn't used.
  • Introduces another divergence from typical HTML elements, further reducing transparency.
  • Changes to the module location can't be made without breaking existing code.
  • import would mean waiting until the module loads in before running the rest of the script.
  • System.import() adds an extra wrapper around a block of code and any custom elements would need to be created inside that wrapper because of the recent no-upgrade decision.
  • Either the element's module has to define itself, which impacts portability and reusability, or the author would need to define it correctly.

It would be nice if we could specify a module to load on-demand in the call to defineElement:

// Library code
[ 'element1', 'element2', 'element3' ].forEach(el =>
    document.defineElement(`my-${el}`, { module: `./${el}.js` }))
// Example module code
export default class Element1 extends HTMLElement {
    // ...
}

This has the additional advantage that elements created with document.createElement() can be safely upgraded (the element's definition would be responsible for letting the author know its API is ready). I think it's certainly more foolproof, which is a boon considering most of our library users aren't "pro" JavaScript developers.

@domenic
Copy link
Collaborator

domenic commented Mar 16, 2016

This isn't a job for the custom elements API. Rather, you should be able to use composable pieces like MutationObserver + the module loader to monitor for instances of a tag name and load modules with corresponding definitions on demand. It's true that the module loader API is not defined yet, but that's an issue to take up with https://github.com/whatwg/loader. (Or, in the meantime, use one of the many existing module systems that do allow dynamic loading, like CJS with bundle splitting, or AMD.)

Note that

System.import() adds an extra wrapper around a block of code and any custom elements would need to be created inside that wrapper because of the recent no-upgrade decision.

will be a problem even with your proposed solution. Even with your syntax, until the module loads you cannot create custom elements for those names. Your syntax is in fact worse since it gives you no way to know when the module loads, unlike System.import() (or whatever the loader spec ends up deciding on). Unless you were proposing sync XHR-like module loads, which of course is not OK.

@andyearnshaw
Copy link
Author

Rather, you should be able to use composable pieces like MutationObserver + the module loader to monitor for instances of a tag name and load modules with corresponding definitions on demand.

That's one option I'd considered, but doesn't cover elements created with document.createElement().

Even with your syntax, until the module loads you cannot create custom elements for those names. Your syntax is in fact worse since it gives you no way to know when the module loads, unlike System.import() (or whatever the loader spec ends up deciding on).

That's not necessarily true for all cases (but for those cases, the custom element could dispatch a "ready" event from its createdCallback), sometimes you need to do is something like this:

let map = document.createElement('x-map'); // triggers module load
map.zoomLevel = 12;
map.long = -1.549077;
map.lat = 53.800755;

In that example, if map was inserted into the main document then the theoretical MO from your first point would trigger and load the module, but if it was inserted into another document or if it were appended to an element in a fragment it wouldn't. This just seems like something that could be made much simpler by the specification.

@domenic
Copy link
Collaborator

domenic commented Mar 17, 2016

That's not necessarily true for all cases (but for those cases, the custom element could dispatch a "ready" event from its createdCallback), sometimes you need to do is something like this:

But this would not work with your syntax either, until the module loads. Those property sets will do nothing.

@andyearnshaw
Copy link
Author

Yeah, thinking on it, I guess it's not a particularly great idea to do it this way. You could replace those property sets with attribute sets but it might cause confusion. I was stuck in the mindset of how our current approach works; we define and re-set the properties in createdCallback as we currently have a requirement to support IE 8. In a proper setup, pre-existing properties would sit on the element in front of those defined on the prototype, which would be problematic.

In our next release we're dropping support for everything before IE 11, so it's a good time to re-evaluate our approach and do things properly. Recommending import and System.import, along with the MO method, is probably the right thing to do.

@treshugart
Copy link

@andyearnshaw setting properties can carry over to a custom element at upgrade time depending on the implementation. See http://jsbin.com/jaqoni/1. The catch here is that the element must be inserted to the document to to be upgraded when the definition is eventually loaded. Specifics around the eventual upgrade behaviour is being discussed at #419 (comment).

EDIT

I just saw that you are aware of that thread. Apologies.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants