It would mean we could take advantage of simple features of DOM that we already have. F.e. writing the following within a shadow root without muddying the global scope:
<!-- inside the shadow DOM -->
<div onclick="foo()"></div>
<script scope="shadow">
// foo does not leak to global scope, but it can shadow a global foo var.
function foo() {...}
</script>
The builtin onclick
attribute handler of the DOM would run in scope of the shadow root, which means it’ll use any variables defined in that “lexical scope”. Any variables in that scope can shadow global variables, just like we’re accustomed to inside JavaScript functions. The only difference between this new scope and a normal function scope is that the variables are also exposed on an object on the shadow root.
Now we can imagine a custom element framework, might prescribe for a user to write HTML files like this:
<!-- my-element.html -->
<div onclick="foo()">
<slot></slot>
</div>
<script>
function foo() {...}
function connected(ce) {...}
function disconnected(ce) {...}
</script>
Then the framework takes this HTML, adds the scope="shadow"
attribute to it, gets the life cycle functions from the scope object, and hooks them up to the custom element class.
Just like that we’d have an easy way to create “single-file component” format (like Vue). This would be alternative to the Declarative Custom Elements proposal, but this does not require ES Modules, and it works with the existing and simple functionality that HTML has already given us for many years (f.e. onclick="foo()"
), which is something we can’t really use with custom elements.
Basically, this could allow possibilities that requires a lower learning curve for new developers.
And even CE authors could hook things up pretty easily:
// fetch the above HTML file
const html = fetch('./my-element.html')
.then(r => r.text().replace('<script', '<script scope="shadow"'))
class MyElement extends HTMLElement {
async connectedCallback () {
const htm = await html
if (!this.isConnected) return
const root = this.attachShadow(...)
// (Let's update the HTML parser for this)
root.innerHTML = htm
this.scope = root.scope
root.scope.connected?(this)
}
disconnectedCallback() {
this.scope?.disconnected?(this)
}
}
With this you could imagine it would be trivial for an author to reduce that boilerplate to the following (with a simple monkey patch):
customeElements.define('my-element', './my-element.html')