element-worklet icon indicating copy to clipboard operation
element-worklet copied to clipboard

What if the custom element author wishes for the end user to define the element name?

Open trusktr opened this issue 4 years ago • 1 comments

A pattern seen in some custom element libraries is that they allow the end user to import the class, and for the end user to decide with which name to call customElements.define().

For example, an end user can do this:

import {NiceElement} from 'some-library'

// End user chooses the tag name:
customElements.define('cool-element', NiceElement)

const el = document.createElement('cool-element')

Maybe we should allow this pattern to still be possible with Element Worklets.

For that to be possible, maybe the worklet can call customElements.define like the proposal already shows, but maybe also the end user can call it if the worklet doesn't. The alternative form might look like this:

// nice-element.js

// Use a default export to denote the class for the ElementWorklet perhaps:
export default class NiceElement extends WorkletElement {...}

Then the end user can register the element:

// end-user.js
customElements.defineWorklet('cool-element', 'path/to/nice-element.js')

const el = document.createElement('cool-element')

Or maybe we can rely on the stage 3 import assertions proposal (I feel like the "import assertions" naming is strange). It could look like this:

// nice-element.js
// This is not a default export (the author can use any type of export):
export class NiceElement extends WorkletElement {...}
// end-user.js
import {NiceElement} from 'path/to/nice-element.js' assert { type: "element-worklet" }

// customElements.define would automatically know how to handle WorkletElement proxy classes:
customElements.define('cool-element', NiceElement)

const el = document.createElement('cool-element')

assert doesn't quite match the semantic though. It seems that assert is for asserting mime types, but the worklet would have the same mime type as any other JSON file. So maybe some other sort of "import attribute" would be needed in a follow on like that proposal mentions:

import {NiceElement} from 'path/to/nice-element.js' with { elementWorklet: true }

or something?

trusktr avatar Aug 16 '21 21:08 trusktr

There are a few reasons why I didn't include this type of registration behavior in the spec/demo:

  1. Allowing registration on the main thread makes worklets main-thread-bound. That means worklets can't boot up and get attached to the DOM until the main thread JS has executed and registered them, defeating parallelism that would otherwise be gained for free.
  2. The class representation obtained from an import of a Worklet would be super hard to specify: we don't have cross-thread references in JS, so it would need to be some sort of opaque reference, which would make things like NiceElement.tagName impossible.
  3. Exporting from a worklet is undefined behavior - Worklets are module scripts, but are not importable module scripts - they work more like a Module Worker, which does not have exports (they get ignored).

That said, I do think there's potentially value in exploring a mapping/renaming mechanism - it could be generic to all Custom Elements:

const result = await customElements.addModule('/worklets.js');
// result is currently `undefined` for all types of Worklet, but it could be given a value for Element Worklet:
console.log(result); // { 'nice-element': NiceElementProxy { observedAttributes:['x'] }, ... }
// aliasing could be achieved via:
customElements.define('cool-element', result['nice-element']);
// or even:
customElements.define('cool-element', customElements.get('nice-element'));

Regarding Import Assertions - they can't be used for something like this. They're not allowed to alter the behavior of an import, only provide a mechanism for verifying (mime)type. There'd also be too much missing/implicit behavior to spec out for an import that spawned 1+ threads.

developit avatar Nov 02 '21 16:11 developit